jackasda211233 commited on
Commit
d896e71
·
verified ·
1 Parent(s): 44a04b8

Upload qwen36-mtp-rys_delta.patch with huggingface_hub

Browse files
Files changed (1) hide show
  1. qwen36-mtp-rys_delta.patch +1292 -0
qwen36-mtp-rys_delta.patch ADDED
@@ -0,0 +1,1292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diff --git a/common/chat.cpp b/common/chat.cpp
2
+ index ed1c0e54..7f3008ca 100644
3
+ --- a/common/chat.cpp
4
+ +++ b/common/chat.cpp
5
+ @@ -28,9 +28,59 @@
6
+ #include <utility>
7
+ #include <vector>
8
+ #include <fstream>
9
+ +#include <cctype>
10
+ +#include <unordered_set>
11
+
12
+ using json = nlohmann::ordered_json;
13
+
14
+ +static std::string trim_copy(const std::string & s) {
15
+ + size_t start = 0;
16
+ + while (start < s.size() && std::isspace(static_cast<unsigned char>(s[start]))) {
17
+ + start++;
18
+ + }
19
+ + size_t end = s.size();
20
+ + while (end > start && std::isspace(static_cast<unsigned char>(s[end - 1]))) {
21
+ + end--;
22
+ + }
23
+ + return s.substr(start, end - start);
24
+ +}
25
+ +
26
+ +static std::string canonicalize_tool_call_arguments(const std::string & arguments, bool is_partial) {
27
+ + std::string out = trim_copy(arguments);
28
+ + if (!is_partial && !out.empty()) {
29
+ + try {
30
+ + const auto parsed = nlohmann::json::parse(out);
31
+ + out = parsed.dump();
32
+ + } catch (const std::exception &) {
33
+ + }
34
+ + }
35
+ + return out;
36
+ +}
37
+ +
38
+ +static void dedupe_tool_calls(std::vector<common_chat_tool_call> & tool_calls, bool is_partial, const common_chat_parser_params & params) {
39
+ + if (!params.parse_tool_calls || tool_calls.size() < 2) {
40
+ + return;
41
+ + }
42
+ +
43
+ + std::unordered_set<std::string> seen;
44
+ + seen.reserve(tool_calls.size());
45
+ +
46
+ + std::vector<common_chat_tool_call> out;
47
+ + out.reserve(tool_calls.size());
48
+ +
49
+ + for (auto & tc : tool_calls) {
50
+ + std::string key = tc.name;
51
+ + key.push_back('\x1f');
52
+ + key += canonicalize_tool_call_arguments(tc.arguments, is_partial);
53
+ +
54
+ + if (seen.insert(key).second) {
55
+ + out.push_back(std::move(tc));
56
+ + }
57
+ + }
58
+ +
59
+ + tool_calls = std::move(out);
60
+ +}
61
+ +
62
+ static std::string format_time(const std::chrono::system_clock::time_point & now, const std::string & format) {
63
+ auto time = std::chrono::system_clock::to_time_t(now);
64
+ auto local_time = *std::localtime(&time);
65
+ @@ -2499,6 +2549,8 @@ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_pars
66
+ }
67
+ mapper->from_ast(ctx.ast, result);
68
+
69
+ + dedupe_tool_calls(msg.tool_calls, is_partial, params);
70
+ +
71
+ if (ctx.is_debug()) {
72
+ fprintf(stderr, "\nAST for partial parse (fail):\n%s\n", ctx.ast.dump().c_str());
73
+ fflush(stderr);
74
+ @@ -2519,6 +2571,7 @@ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_pars
75
+ mapper = std::make_unique<common_chat_peg_mapper>(msg);
76
+ }
77
+ mapper->from_ast(ctx.ast, result);
78
+ + dedupe_tool_calls(msg.tool_calls, is_partial, params);
79
+
80
+ if (ctx.is_debug()) {
81
+ fprintf(stderr, "\nAST for %s parse:\n%s\n", is_partial ? "partial" : "full", ctx.ast.dump().c_str());
82
+ diff --git a/common/common.cpp b/common/common.cpp
83
+ index d308fe9d..9b580ae1 100644
84
+ --- a/common/common.cpp
85
+ +++ b/common/common.cpp
86
+ @@ -1054,6 +1054,10 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa
87
+ params.speculative.autotune = true;
88
+ return true;
89
+ }
90
+ + if (arg == "--mtp-adaptive") {
91
+ + params.speculative.mtp_adaptive = true;
92
+ + return true;
93
+ + }
94
+ if (arg == "--chunks") {
95
+ CHECK_ARG
96
+ params.n_chunks = std::stoi(argv[i]);
97
+ @@ -2745,6 +2749,7 @@ void gpt_params_print_usage(int /*argc*/, char ** argv, const gpt_params & param
98
+ options.push_back({ "*", "-hft, --hf-token TOKEN", "Hugging Face access token (default: value from HF_TOKEN environment variable)" });
99
+ options.push_back({ "*", "-mtp, --multi-token-prediction", "whether to use multi-token-prediction (if supported) (default: %s)", params.has_mtp ? "true" : "false" });
100
+ options.push_back({ "*", "-no-mtp, --no-multi-token-prediction", "whether to use multi-token-prediction (if supported) (default: %s)", !params.has_mtp ? "true" : "false" });
101
+ + options.push_back({ "*", "--mtp-adaptive", "server-side MTP gate: calibrate no-MTP speed, then disable MTP on poor recent speed or acceptance" });
102
+ options.push_back({ "*", "--draft-max, --draft, --draft-n N",
103
+ "number of tokens to draft for speculative decoding (default: %d)", params.speculative.n_max });
104
+ options.push_back({ "*", "--draft-min, --draft-n-min N", "minimum number of draft tokens to use for speculative decoding" });
105
+ diff --git a/common/common.h b/common/common.h
106
+ index 734d93de..423ca846 100644
107
+ --- a/common/common.h
108
+ +++ b/common/common.h
109
+ @@ -210,6 +210,7 @@ struct common_params_speculative {
110
+ std::string cache_type_v = ""; // KV cache data type for V for the draft model
111
+
112
+ bool autotune = false; // automatically optimize speculative params for max tokens/sec
113
+ + bool mtp_adaptive = false; // server-side MTP runtime gate
114
+
115
+ bool has_dft() const {
116
+ return !model.empty() || !params.empty();
117
+ diff --git a/common/speculative.cpp b/common/speculative.cpp
118
+ index d63edd74..70151855 100644
119
+ --- a/common/speculative.cpp
120
+ +++ b/common/speculative.cpp
121
+ @@ -12,6 +12,7 @@
122
+
123
+ #include <algorithm>
124
+ #include <cstring>
125
+ +#include <cstdlib>
126
+ #include <iomanip>
127
+ #include <map>
128
+
129
+ @@ -1353,6 +1354,11 @@ void common_speculative_context_shift(
130
+ }
131
+ }
132
+
133
+ +static bool mtp_draft_gpu_argmax_enabled() {
134
+ + const char * value = std::getenv("LLAMA_MTP_DRAFT_GPU_ARGMAX");
135
+ + return value != nullptr && std::strcmp(value, "0") != 0;
136
+ +}
137
+ +
138
+ std::vector<llama_token> mtp_speculative_gen_draft(
139
+ struct common_sampler * smpl,
140
+ struct llama_context * ctx,
141
+ @@ -1370,6 +1376,9 @@ std::vector<llama_token> mtp_speculative_gen_draft(
142
+ common_sampler_reset(smpl);
143
+
144
+ llama_batch mtp_batch = llama_batch_init(1, 0, 1);
145
+ + const bool use_gpu_argmax = p_min <= 0.0f && mtp_draft_gpu_argmax_enabled();
146
+ +
147
+ + llama_set_mtp_draft_gpu_argmax(ctx, use_gpu_argmax);
148
+ llama_set_mtp_op_type(ctx, MTP_OP_DRAFT_GEN);
149
+
150
+ llama_token current_input_id = id_last;
151
+ @@ -1383,9 +1392,17 @@ std::vector<llama_token> mtp_speculative_gen_draft(
152
+ break;
153
+ }
154
+
155
+ - float prob;
156
+ - llama_token id_next = common_sampler_sample_speculative(smpl, ctx, 0, &prob);
157
+ + float prob = 1.0f;
158
+ + llama_token id_next = LLAMA_TOKEN_NULL;
159
+ + if (use_gpu_argmax) {
160
+ + id_next = llama_get_mtp_draft_argmax_ith(ctx, 0);
161
+ + } else {
162
+ + id_next = common_sampler_sample_speculative(smpl, ctx, 0, p_min > 0.0f ? &prob : nullptr);
163
+ + }
164
+
165
+ + if (id_next == LLAMA_TOKEN_NULL) {
166
+ + break;
167
+ + }
168
+ drafts.push_back(id_next);
169
+
170
+ const float * emb = llama_get_embeddings_ith(ctx, 0);
171
+ @@ -1403,10 +1420,8 @@ std::vector<llama_token> mtp_speculative_gen_draft(
172
+ llama_batch_free(mtp_batch);
173
+ llama_set_mtp_op_type(ctx, MTP_OP_NONE);
174
+
175
+ - // Purge the metadata for the draft tokens.
176
+ - // This prevents cache state corruption where two cells map to the same logical position.
177
+ - if (!drafts.empty()) {
178
+ - llama_kv_cache_seq_rm(ctx, seq_id, n_past, current_n_past);
179
+ + if (current_n_past > n_past + 1) {
180
+ + llama_kv_cache_seq_rm(ctx, seq_id, n_past + 1, current_n_past);
181
+ }
182
+
183
+ return drafts;
184
+ @@ -1436,7 +1451,7 @@ void mtp_update_kv_cache(struct llama_context * ctx, const llama_batch& batch, b
185
+ }
186
+
187
+ for (int i = 0; i < mtp_batch.n_tokens; ++i) {
188
+ - mtp_batch.logits[i] = true;
189
+ + mtp_batch.logits[i] = false;
190
+ }
191
+ llama_decode(ctx, mtp_batch);
192
+ llama_set_mtp_op_type(ctx, MTP_OP_NONE);
193
+ @@ -1452,8 +1467,12 @@ void mtp_accept_tokens(
194
+ return;
195
+ }
196
+
197
+ - llama_batch accepted_batch = llama_batch_init(ids.size(), 0, 1);
198
+ - for (size_t i = 0; i < ids.size(); ++i) {
199
+ + if (ids.size() == 1) {
200
+ + return;
201
+ + }
202
+ +
203
+ + llama_batch accepted_batch = llama_batch_init(ids.size() - 1, 0, 1);
204
+ + for (size_t i = 1; i < ids.size(); ++i) {
205
+ common_batch_add(accepted_batch, ids[i], n_past_base + i, { seq_id }, true);
206
+ }
207
+
208
+ diff --git a/examples/imatrix/imatrix.cpp b/examples/imatrix/imatrix.cpp
209
+ index 8bc44587..6be1eb98 100644
210
+ --- a/examples/imatrix/imatrix.cpp
211
+ +++ b/examples/imatrix/imatrix.cpp
212
+ @@ -638,6 +638,37 @@ static void process_logits(
213
+ }
214
+ }
215
+
216
+ +static bool run_mtp_imatrix_warmup(llama_context * ctx, llama_token * tokens, const float * hidden_states, int32_t n_tokens, llama_pos pos_0) {
217
+ + const llama_model * model = llama_get_model(ctx);
218
+ + if (llama_model_n_nextn_layer(model) <= 0) {
219
+ + return true;
220
+ + }
221
+ +
222
+ + llama_batch mtp_batch = llama_batch_init(n_tokens, 0, 1);
223
+ + mtp_batch.n_tokens = n_tokens;
224
+ + for (int32_t i = 0; i < n_tokens; ++i) {
225
+ + mtp_batch.token[i] = tokens[i];
226
+ + mtp_batch.pos[i] = pos_0 + i;
227
+ + mtp_batch.n_seq_id[i] = 1;
228
+ + mtp_batch.seq_id[i][0] = 0;
229
+ + mtp_batch.logits[i] = 1;
230
+ + }
231
+ +
232
+ + llama_set_draft_input_hidden_state(ctx, hidden_states);
233
+ + llama_set_mtp_op_type(ctx, MTP_OP_WARMUP);
234
+ + const int ret = llama_decode(ctx, mtp_batch);
235
+ + llama_set_mtp_op_type(ctx, MTP_OP_NONE);
236
+ + llama_set_draft_input_hidden_state(ctx, nullptr);
237
+ + llama_batch_free(mtp_batch);
238
+ +
239
+ + if (ret != 0) {
240
+ + fprintf(stderr, "%s: failed to eval MTP warmup batch\n", __func__);
241
+ + return false;
242
+ + }
243
+ +
244
+ + return true;
245
+ +}
246
+ +
247
+ static bool compute_imatrix(llama_context * ctx, const gpt_params & params) {
248
+ const bool add_bos = llama_should_add_bos_token(llama_get_model(ctx));
249
+ GGML_ASSERT(llama_add_eos_token(llama_get_model(ctx)) != 1);
250
+ @@ -680,12 +711,17 @@ static bool compute_imatrix(llama_context * ctx, const gpt_params & params) {
251
+ const int n_chunk = params.n_chunks < 0 ? n_chunk_max : std::min(params.n_chunks, n_chunk_max);
252
+ const int n_vocab = llama_n_vocab(llama_get_model(ctx));
253
+ const int n_batch = params.n_batch;
254
+ + const bool collect_mtp = params.has_mtp && llama_model_n_nextn_layer(llama_get_model(ctx)) > 0;
255
+ + const int n_embd = collect_mtp ? llama_model_n_embd(llama_get_model(ctx)) : 0;
256
+
257
+ int count = 0;
258
+ double nll = 0.0;
259
+ double nll2 = 0.0;
260
+
261
+ fprintf(stderr, "%s: computing over %d chunks with batch_size %d\n", __func__, n_chunk, n_batch);
262
+ + if (collect_mtp) {
263
+ + fprintf(stderr, "%s: MTP warmup collection enabled\n", __func__);
264
+ + }
265
+
266
+ std::vector<std::thread> workers(std::thread::hardware_concurrency() - 1);
267
+
268
+ @@ -701,6 +737,18 @@ static bool compute_imatrix(llama_context * ctx, const gpt_params & params) {
269
+ const int end = start + n_ctx;
270
+
271
+ std::vector<float> logits;
272
+ + if (params.compute_ppl && collect_mtp) {
273
+ + logits.reserve((size_t)n_ctx * n_vocab);
274
+ + }
275
+ + std::vector<float> mtp_hidden_states;
276
+ + std::vector<llama_token> mtp_tokens;
277
+ + if (collect_mtp) {
278
+ + mtp_hidden_states.resize((size_t)n_ctx * n_embd);
279
+ + mtp_tokens.assign(tokens.begin() + start, tokens.begin() + end);
280
+ + if (add_bos) {
281
+ + mtp_tokens[0] = llama_token_bos(llama_get_model(ctx));
282
+ + }
283
+ + }
284
+
285
+ const auto t_start = std::chrono::high_resolution_clock::now();
286
+
287
+ @@ -725,12 +773,36 @@ static bool compute_imatrix(llama_context * ctx, const gpt_params & params) {
288
+ return false;
289
+ }
290
+
291
+ + if (params.compute_ppl && (num_batches > 1 || collect_mtp)) {
292
+ + const auto * batch_logits = llama_get_logits(ctx);
293
+ + logits.insert(logits.end(), batch_logits, batch_logits + batch_size * n_vocab);
294
+ + }
295
+ +
296
+ + if (collect_mtp) {
297
+ + float * hidden_dst = mtp_hidden_states.data() + (size_t)j * n_batch * n_embd;
298
+ + for (int k = 0; k < batch_size; ++k) {
299
+ + const float * emb = llama_get_embeddings_ith(ctx, k);
300
+ + if (!emb) {
301
+ + fprintf(stderr, "%s: failed to read main-model hidden state for token %d\n", __func__, k);
302
+ + return false;
303
+ + }
304
+ + std::memcpy(hidden_dst + (size_t)k * n_embd, emb, (size_t)n_embd * sizeof(float));
305
+ + }
306
+ + }
307
+ +
308
+ // restore the original token in case it was set to BOS
309
+ tokens[batch_start] = token_org;
310
+ + }
311
+
312
+ - if (params.compute_ppl && num_batches > 1) {
313
+ - const auto * batch_logits = llama_get_logits(ctx);
314
+ - logits.insert(logits.end(), batch_logits, batch_logits + batch_size * n_vocab);
315
+ + if (collect_mtp) {
316
+ + llama_kv_cache_clear(ctx);
317
+ + const int mtp_batch = std::max<int>(1, params.n_ubatch);
318
+ + for (int mtp_start = 0; mtp_start < n_ctx; mtp_start += mtp_batch) {
319
+ + const int mtp_size = std::min(n_ctx - mtp_start, mtp_batch);
320
+ + if (!run_mtp_imatrix_warmup(ctx, mtp_tokens.data() + mtp_start,
321
+ + mtp_hidden_states.data() + (size_t)mtp_start * n_embd, mtp_size, mtp_start)) {
322
+ + return false;
323
+ + }
324
+ }
325
+ }
326
+
327
+ @@ -749,7 +821,7 @@ static bool compute_imatrix(llama_context * ctx, const gpt_params & params) {
328
+
329
+ if (params.compute_ppl) {
330
+ const int first = n_ctx/2;
331
+ - const auto all_logits = num_batches > 1 ? logits.data() : llama_get_logits(ctx);
332
+ + const auto all_logits = !logits.empty() ? logits.data() : llama_get_logits(ctx);
333
+ process_logits(n_vocab, all_logits + first*n_vocab, tokens.data() + start + first, n_ctx - 1 - first,
334
+ workers, nll, nll2, logit_history.data() + start + first, prob_history.data() + start + first);
335
+ count += n_ctx - first - 1;
336
+ diff --git a/examples/server/server-context.cpp b/examples/server/server-context.cpp
337
+ index b38d13a0..347213f3 100644
338
+ --- a/examples/server/server-context.cpp
339
+ +++ b/examples/server/server-context.cpp
340
+ @@ -22,6 +22,107 @@ static void log_text(const gpt_params & params_base, const std::string & text) {
341
+ }
342
+ }
343
+
344
+ +static constexpr int32_t MTP_ADAPTIVE_BASELINE_TOKENS = 4;
345
+ +static constexpr int32_t MTP_ADAPTIVE_MIN_WINDOWS = 4;
346
+ +static constexpr int32_t MTP_ADAPTIVE_MAX_BAD_WINDOWS = 2;
347
+ +static constexpr double MTP_ADAPTIVE_EMA_ALPHA = 0.25;
348
+ +static constexpr double MTP_ADAPTIVE_MIN_ACCEPT = 0.25;
349
+ +static constexpr double MTP_ADAPTIVE_MIN_TPS_RATIO = 0.98;
350
+ +
351
+ +static void mtp_adaptive_update_ema(double & ema, double value) {
352
+ + if (ema <= 0.0) {
353
+ + ema = value;
354
+ + } else {
355
+ + ema = MTP_ADAPTIVE_EMA_ALPHA * value + (1.0 - MTP_ADAPTIVE_EMA_ALPHA) * ema;
356
+ + }
357
+ +}
358
+ +
359
+ +static bool mtp_adaptive_enabled(const server_slot & slot) {
360
+ + return slot.has_mtp && slot.params.speculative.mtp_adaptive;
361
+ +}
362
+ +
363
+ +static void mtp_adaptive_disable(server_slot & slot, const char * reason, double value, double threshold) {
364
+ + if (slot.mtp_adaptive_disabled) {
365
+ + return;
366
+ + }
367
+ +
368
+ + slot.mtp_adaptive_disabled = true;
369
+ + slot.mtp_adaptive_step_start_us = 0;
370
+ + slot.mtp_adaptive_no_mtp_step_start_us = 0;
371
+ +
372
+ + SLT_WRN(slot,
373
+ + "adaptive MTP disabled: %s (value %.3f, threshold %.3f, no_mtp_tps %.2f, mtp_tps %.2f, accept %.3f)\n",
374
+ + reason, value, threshold, slot.mtp_adaptive_no_mtp_tps, slot.mtp_adaptive_mtp_tps,
375
+ + slot.mtp_adaptive_accept);
376
+ +}
377
+ +
378
+ +static void mtp_adaptive_note_no_mtp(server_slot & slot, int64_t t_now_us) {
379
+ + if (!mtp_adaptive_enabled(slot) || slot.mtp_adaptive_disabled || slot.mtp_adaptive_no_mtp_step_start_us <= 0) {
380
+ + return;
381
+ + }
382
+ +
383
+ + const int64_t elapsed_us = t_now_us - slot.mtp_adaptive_no_mtp_step_start_us;
384
+ + slot.mtp_adaptive_no_mtp_step_start_us = 0;
385
+ + if (elapsed_us <= 100) {
386
+ + return;
387
+ + }
388
+ +
389
+ + mtp_adaptive_update_ema(slot.mtp_adaptive_no_mtp_tps, 1e6 / (double) elapsed_us);
390
+ + slot.mtp_adaptive_baseline_seen++;
391
+ +
392
+ + if (slot.mtp_adaptive_baseline_seen == MTP_ADAPTIVE_BASELINE_TOKENS) {
393
+ + SLT_DBG(slot, "adaptive MTP no-MTP baseline ready: %.2f tok/s over %d tokens\n",
394
+ + slot.mtp_adaptive_no_mtp_tps, slot.mtp_adaptive_baseline_seen);
395
+ + }
396
+ +}
397
+ +
398
+ +static void mtp_adaptive_note_mtp(server_slot & slot, size_t n_draft, size_t n_output, int64_t t_now_us) {
399
+ + if (!mtp_adaptive_enabled(slot) || slot.mtp_adaptive_disabled || n_draft == 0) {
400
+ + return;
401
+ + }
402
+ +
403
+ + const size_t n_accepted = n_output > 0 ? n_output - 1 : 0;
404
+ + const double acceptance = (double) n_accepted / (double) n_draft;
405
+ + mtp_adaptive_update_ema(slot.mtp_adaptive_accept, acceptance);
406
+ + slot.mtp_adaptive_windows++;
407
+ +
408
+ + if (slot.mtp_adaptive_step_start_us > 0) {
409
+ + const int64_t elapsed_us = t_now_us - slot.mtp_adaptive_step_start_us;
410
+ + slot.mtp_adaptive_step_start_us = 0;
411
+ + if (elapsed_us > 100 && n_output > 0) {
412
+ + mtp_adaptive_update_ema(slot.mtp_adaptive_mtp_tps, (double) n_output * 1e6 / (double) elapsed_us);
413
+ + }
414
+ + }
415
+ +
416
+ + if (slot.mtp_adaptive_windows < MTP_ADAPTIVE_MIN_WINDOWS) {
417
+ + return;
418
+ + }
419
+ +
420
+ + if (slot.mtp_adaptive_accept < MTP_ADAPTIVE_MIN_ACCEPT) {
421
+ + slot.mtp_adaptive_low_accept++;
422
+ + } else {
423
+ + slot.mtp_adaptive_low_accept = 0;
424
+ + }
425
+ +
426
+ + if (slot.mtp_adaptive_low_accept >= MTP_ADAPTIVE_MAX_BAD_WINDOWS) {
427
+ + mtp_adaptive_disable(slot, "low acceptance", slot.mtp_adaptive_accept, MTP_ADAPTIVE_MIN_ACCEPT);
428
+ + return;
429
+ + }
430
+ +
431
+ + if (slot.mtp_adaptive_baseline_seen >= MTP_ADAPTIVE_BASELINE_TOKENS &&
432
+ + slot.mtp_adaptive_no_mtp_tps > 0.0 && slot.mtp_adaptive_mtp_tps > 0.0 &&
433
+ + slot.mtp_adaptive_mtp_tps < slot.mtp_adaptive_no_mtp_tps * MTP_ADAPTIVE_MIN_TPS_RATIO) {
434
+ + slot.mtp_adaptive_slow_windows++;
435
+ + } else {
436
+ + slot.mtp_adaptive_slow_windows = 0;
437
+ + }
438
+ +
439
+ + if (slot.mtp_adaptive_slow_windows >= MTP_ADAPTIVE_MAX_BAD_WINDOWS) {
440
+ + mtp_adaptive_disable(slot, "slower than no-MTP", slot.mtp_adaptive_mtp_tps,
441
+ + slot.mtp_adaptive_no_mtp_tps * MTP_ADAPTIVE_MIN_TPS_RATIO);
442
+ + }
443
+ +}
444
+ +
445
+ void server_speculative_checkpoint::clear() {
446
+ valid = false;
447
+ per_step_enabled = false;
448
+ @@ -456,6 +557,16 @@ void server_slot::reset() {
449
+ // Reset speculative decoding stats
450
+ n_draft_total = 0;
451
+ n_draft_accepted = 0;
452
+ + mtp_adaptive_disabled = false;
453
+ + mtp_adaptive_baseline_seen = 0;
454
+ + mtp_adaptive_windows = 0;
455
+ + mtp_adaptive_low_accept = 0;
456
+ + mtp_adaptive_slow_windows = 0;
457
+ + mtp_adaptive_step_start_us = 0;
458
+ + mtp_adaptive_no_mtp_step_start_us = 0;
459
+ + mtp_adaptive_no_mtp_tps = 0.0;
460
+ + mtp_adaptive_mtp_tps = 0.0;
461
+ + mtp_adaptive_accept = 0.0;
462
+ chat_msg = {};
463
+ json_schema = json();
464
+ generated_tool_call_ids.clear();
465
+ @@ -510,13 +621,19 @@ void server_slot::add_token_string(const completion_token_output& token) {
466
+ }
467
+
468
+ bool server_slot::can_speculate() const {
469
+ - return (!!spec || has_mtp);
470
+ + return !mtp_adaptive_disabled && (!!spec || has_mtp);
471
+ }
472
+
473
+ int server_slot::get_n_draft_max() const {
474
+ if (!can_speculate()) {
475
+ return 0;
476
+ }
477
+ + if (has_mtp && params.speculative.mtp_adaptive &&
478
+ + mtp_adaptive_baseline_seen < MTP_ADAPTIVE_BASELINE_TOKENS) {
479
+ + SLT_DBG(*this, "adaptive MTP collecting no-MTP baseline: %d/%d\n",
480
+ + mtp_adaptive_baseline_seen, MTP_ADAPTIVE_BASELINE_TOKENS);
481
+ + return 0;
482
+ + }
483
+
484
+ // determine the max draft that fits the current slot state
485
+ int n_draft_max = params.speculative.n_max;
486
+ @@ -1049,6 +1166,7 @@ bool server_context::launch_slot_with_task(server_slot& slot, server_task& task)
487
+ slot.params.speculative.n_max = json_value(data, "speculative.n_max", params_base.speculative.n_max);
488
+ slot.params.speculative.n_min = json_value(data, "speculative.n_min", params_base.speculative.n_min);
489
+ slot.params.speculative.p_min = json_value(data, "speculative.p_min", params_base.speculative.p_min);
490
+ + slot.params.speculative.mtp_adaptive = json_value(data, "speculative.mtp_adaptive", defaults.speculative.mtp_adaptive);
491
+
492
+ slot.params.speculative.n_min = std::min(slot.params.speculative.n_max, slot.params.speculative.n_min);
493
+ slot.params.speculative.n_min = std::max(slot.params.speculative.n_min, 0);
494
+ @@ -1608,6 +1726,10 @@ bool server_context::launch_slot_with_task(server_slot& slot, server_task& task)
495
+ bool do_checkpoint = params_base.ctx_checkpoints_n > 0;
496
+ // make checkpoints only for completion tasks
497
+ do_checkpoint = do_checkpoint && task.type == SERVER_TASK_TYPE_COMPLETION;
498
+ + if (do_checkpoint && llama_model_is_split_mode_graph(llama_get_model(slot.ctx))) {
499
+ + LLAMA_LOG_WARN("%s: disabling recurrent checkpoints for split-mode graph; partial sequence snapshots are unstable on this path\n", __func__);
500
+ + do_checkpoint = false;
501
+ + }
502
+ // make a checkpoint of the parts of the memory that cannot be rolled back.
503
+ // checkpoints are created only if:
504
+ // - the model architecture is marked as recurrent or hybrid
505
+ @@ -3155,6 +3277,10 @@ void server_context::add_sampled_tokens() {
506
+ }
507
+ }
508
+
509
+ + if (mtp_adaptive_enabled(slot)) {
510
+ + slot.mtp_adaptive_step_start_us = ggml_time_us();
511
+ + }
512
+ +
513
+ llama_tokens draft = common_speculative_draft(slot.spec, params_spec, cached_text_tokens, slot.sampled);
514
+
515
+ const int n_draft_max = slot.get_n_draft_max();
516
+ @@ -3180,6 +3306,7 @@ void server_context::add_sampled_tokens() {
517
+ slot.i_batch = slot.i_batch_dft[0];
518
+ slot.drafted.clear();
519
+ slot.i_batch_dft.clear();
520
+ + slot.mtp_adaptive_step_start_us = 0;
521
+ }
522
+ else {
523
+ // keep track of total number of drafted tokens tested
524
+ @@ -3197,6 +3324,9 @@ void server_context::add_sampled_tokens() {
525
+ else {
526
+ // no speculative decoding
527
+ slot.i_batch = batch.n_tokens;
528
+ + if (mtp_adaptive_enabled(slot) && !slot.mtp_adaptive_disabled) {
529
+ + slot.mtp_adaptive_no_mtp_step_start_us = ggml_time_us();
530
+ + }
531
+
532
+ common_batch_add(batch, slot.sampled, slot.cache_tokens.pos_next(), { slot.id }, true);
533
+
534
+ @@ -3349,6 +3479,10 @@ bool server_context::create_checkpoint(server_slot & slot) {
535
+ }
536
+
537
+ void server_context::batch_pending_prompt(const int32_t n_ubatch, const int32_t n_batch, int32_t & batch_type) {
538
+ + const bool serialize_recurrent_graph_prompts =
539
+ + llama_model_has_recurrent(llama_get_model(ctx)) &&
540
+ + llama_model_is_split_mode_graph(llama_get_model(ctx));
541
+ +
542
+ if (params_base.cont_batching || batch.n_tokens == 0) {
543
+ for (auto& slot : slots) {
544
+ // this slot still has a prompt to be processed
545
+ @@ -3688,6 +3822,10 @@ void server_context::batch_pending_prompt(const int32_t n_ubatch, const int32_t
546
+ }
547
+ }
548
+
549
+ + if (serialize_recurrent_graph_prompts && batch.n_tokens > 0) {
550
+ + break;
551
+ + }
552
+ +
553
+ if (batch.n_tokens >= n_batch) {
554
+ break;
555
+ }
556
+ @@ -3770,11 +3908,22 @@ static void restore_speculative_checkpoint(
557
+ common_batch_add(re_batch, ids[j], slot.spec_ckpt.n_past + 1 + j, { slot.id }, j == n_re - 2);
558
+ }
559
+
560
+ + const int n_embd = slot.has_mtp ? llama_model_n_embd(llama_get_model(ctx)) : 0;
561
+ + const size_t mtp_hidden_state_needed = (size_t)n_re * (size_t)n_embd;
562
+ + const bool can_reuse_mtp_hidden_state =
563
+ + slot.has_mtp &&
564
+ + n_embd > 0 &&
565
+ + mtp_hidden_state_pre.size() >= mtp_hidden_state_needed;
566
+ + const bool need_redecode_logits =
567
+ + slot.sparams.n_probs > 0 && !slot.params.post_sampling_probs;
568
+ + const bool need_redecode_mtp_hidden_state =
569
+ + slot.has_mtp && !can_reuse_mtp_hidden_state;
570
+ +
571
+ if (slot.has_mtp) {
572
+ for (int j = 0; j < re_batch.n_tokens; j++) {
573
+ - re_batch.logits[j] = true;
574
+ + re_batch.logits[j] = need_redecode_mtp_hidden_state || need_redecode_logits;
575
+ }
576
+ - llama_set_embeddings(ctx, true);
577
+ + llama_set_embeddings(ctx, need_redecode_mtp_hidden_state);
578
+ }
579
+
580
+ const int ret = llama_decode(ctx, re_batch);
581
+ @@ -3782,14 +3931,18 @@ static void restore_speculative_checkpoint(
582
+ SLT_ERR(slot, "failed to re-decode accepted tokens after checkpoint restore: %d\n", ret);
583
+ }
584
+ if (slot.has_mtp) {
585
+ - const int n_embd = llama_model_n_embd(llama_get_model(ctx));
586
+ -
587
+ const int n_accepted = (int)ids.size();
588
+ - slot.mtp_hidden_state.resize(n_accepted * n_embd);
589
+ - for (int j = 0; j < n_accepted; j++) {
590
+ - const float * emb_j = llama_get_embeddings_ith(ctx, j);
591
+ - if (emb_j) {
592
+ - memcpy(slot.mtp_hidden_state.data() + j * n_embd, emb_j, n_embd * sizeof(float));
593
+ + if (can_reuse_mtp_hidden_state) {
594
+ + slot.mtp_hidden_state.assign(
595
+ + mtp_hidden_state_pre.begin(),
596
+ + mtp_hidden_state_pre.begin() + mtp_hidden_state_needed);
597
+ + } else {
598
+ + slot.mtp_hidden_state.resize(n_accepted * n_embd);
599
+ + for (int j = 0; j < n_accepted; j++) {
600
+ + const float * emb_j = llama_get_embeddings_ith(ctx, j);
601
+ + if (emb_j) {
602
+ + memcpy(slot.mtp_hidden_state.data() + j * n_embd, emb_j, n_embd * sizeof(float));
603
+ + }
604
+ }
605
+ }
606
+
607
+ @@ -3811,8 +3964,8 @@ static void restore_speculative_checkpoint(
608
+ }
609
+
610
+ llama_batch_free(re_batch);
611
+ - SLT_DBG(slot, "spec checkpoint restored: re-decoded %d tokens (rejected %d drafts)\n",
612
+ - n_re, (int)(n_draft - (ids.size() - 1)));
613
+ + SLT_DBG(slot, "spec checkpoint restored: re-decoded %d tokens (rejected %d drafts, reused_mtp_hidden=%d)\n",
614
+ + n_re, (int)(n_draft - (ids.size() - 1)), can_reuse_mtp_hidden_state ? 1 : 0);
615
+ }
616
+ }
617
+
618
+ @@ -3862,6 +4015,7 @@ void server_context::speculative_decoding_accept() {
619
+ slot.n_decoded += ids.size();
620
+ const int64_t t_current = ggml_time_us();
621
+ slot.t_token_generation = std::max<int64_t>(1, t_current - slot.t_start_generation) / 1e3;
622
+ + mtp_adaptive_note_mtp(slot, n_draft, ids.size(), t_current);
623
+
624
+ // update how many tokens out of those tested were accepted
625
+ slot.n_draft_accepted += ids.size() - 1;
626
+ @@ -4363,6 +4517,7 @@ void server_context::process_batch_tokens(int32_t & n_batch) {
627
+ }
628
+
629
+ slot.t_token_generation = std::max<int64_t>(1, t_current - slot.t_start_generation) / 1e3;
630
+ + mtp_adaptive_note_no_mtp(slot, ggml_time_us());
631
+
632
+ result.tok = id;
633
+ result.prob = 1.0f; // TODO: set it here instead of doing inside populate_token_probs
634
+ diff --git a/examples/server/server-context.h b/examples/server/server-context.h
635
+ index 074787b5..63d6b8bb 100644
636
+ --- a/examples/server/server-context.h
637
+ +++ b/examples/server/server-context.h
638
+ @@ -169,6 +169,16 @@ struct server_slot {
639
+
640
+ bool has_mtp = false;
641
+ std::vector<float> mtp_hidden_state;
642
+ + bool mtp_adaptive_disabled = false;
643
+ + int32_t mtp_adaptive_baseline_seen = 0;
644
+ + int32_t mtp_adaptive_windows = 0;
645
+ + int32_t mtp_adaptive_low_accept = 0;
646
+ + int32_t mtp_adaptive_slow_windows = 0;
647
+ + int64_t mtp_adaptive_step_start_us = 0;
648
+ + int64_t mtp_adaptive_no_mtp_step_start_us = 0;
649
+ + double mtp_adaptive_no_mtp_tps = 0.0;
650
+ + double mtp_adaptive_mtp_tps = 0.0;
651
+ + double mtp_adaptive_accept = 0.0;
652
+
653
+ // saves recurrent state before a speculative batch so it can be restored on rejection
654
+ server_speculative_checkpoint spec_ckpt;
655
+ diff --git a/examples/server/server.cpp b/examples/server/server.cpp
656
+ index feaf1b4e..8151e1e5 100644
657
+ --- a/examples/server/server.cpp
658
+ +++ b/examples/server/server.cpp
659
+ @@ -1086,6 +1086,21 @@ int main(int argc, char ** argv) {
660
+ const std::string oaicompat_model_name = requested_model_name.empty()
661
+ ? fallback_model_name
662
+ : requested_model_name;
663
+ +
664
+ + const auto infer_id_slot_from_model = [](const std::string & model) -> int {
665
+ + const auto ends_with = [](const std::string & s, const std::string & suffix) -> bool {
666
+ + return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
667
+ + };
668
+ +
669
+ + if (ends_with(model, "-slot0") || ends_with(model, "-s0")) {
670
+ + return 0;
671
+ + }
672
+ + if (ends_with(model, "-slot1") || ends_with(model, "-s1")) {
673
+ + return 1;
674
+ + }
675
+ + return -1;
676
+ + };
677
+ + const int inferred_id_slot = infer_id_slot_from_model(oaicompat_model_name);
678
+ for (size_t i = 0; i < inputs.size(); i++) {
679
+ server_task task = server_task(type);
680
+
681
+ @@ -1099,6 +1114,10 @@ int main(int argc, char ** argv) {
682
+ // ctx_server.params,
683
+ // data);
684
+ task.id_slot = json_value(data, "id_slot", -1);
685
+ + if (task.id_slot < 0 && inferred_id_slot >= 0 && inferred_id_slot < ctx_server.params_base.n_parallel) {
686
+ + task.id_slot = inferred_id_slot;
687
+ + task.data["id_slot"] = task.id_slot;
688
+ + }
689
+
690
+ // OAI-compat
691
+ task.params.oaicompat = oaicompat;
692
+ @@ -1254,18 +1273,33 @@ int main(int argc, char ** argv) {
693
+ };
694
+
695
+ const auto handle_models = [&params, &model_meta](const httplib::Request & req, httplib::Response & res) {
696
+ + (void) req;
697
+ +
698
+ + json data = json::array();
699
+ +
700
+ + const auto add_model = [&](const std::string & id, const json & extra_meta = json::object()) {
701
+ + json meta = model_meta;
702
+ + for (const auto & kv : extra_meta.items()) {
703
+ + meta[kv.key()] = kv.value();
704
+ + }
705
+ + data.push_back({
706
+ + {"id", id},
707
+ + {"object", "model"},
708
+ + {"created", std::time(0)},
709
+ + {"owned_by", "llamacpp"},
710
+ + {"meta", meta},
711
+ + {"max_model_len", params.n_ctx},
712
+ + });
713
+ + };
714
+ +
715
+ + add_model(params.model_alias);
716
+ + for (int32_t i = 0; i < params.n_parallel; ++i) {
717
+ + add_model(params.model_alias + std::string("-slot") + std::to_string(i), {{"slot_pinned", i}});
718
+ + }
719
+ +
720
+ json models = {
721
+ {"object", "list"},
722
+ - {"data", {
723
+ - {
724
+ - {"id", params.model_alias},
725
+ - {"object", "model"},
726
+ - {"created", std::time(0)},
727
+ - {"owned_by", "llamacpp"},
728
+ - {"meta", model_meta},
729
+ - {"max_model_len", params.n_ctx}, //vllm specs
730
+ - },
731
+ - }}
732
+ + {"data", data},
733
+ };
734
+
735
+ res.set_content(models.dump(), "application/json; charset=utf-8");
736
+ diff --git a/include/llama.h b/include/llama.h
737
+ index ac0a275b..5eb54e46 100644
738
+ --- a/include/llama.h
739
+ +++ b/include/llama.h
740
+ @@ -1562,6 +1562,10 @@ LLAMA_API struct llama_grammar* llama_sampler_init_grammar_lazy_patterns(
741
+
742
+ LLAMA_API void llama_set_draft_input_hidden_state(struct llama_context * ctx, const float * hidden_state);
743
+
744
+ + LLAMA_API void llama_set_mtp_draft_gpu_argmax(struct llama_context * ctx, bool enabled);
745
+ +
746
+ + LLAMA_API llama_token llama_get_mtp_draft_argmax_ith(struct llama_context * ctx, int32_t i);
747
+ +
748
+ #ifdef __cplusplus
749
+ }
750
+ #endif
751
+ diff --git a/src/graphs/build_qwen35.cpp b/src/graphs/build_qwen35.cpp
752
+ index fb19d679..8e3d939b 100644
753
+ --- a/src/graphs/build_qwen35.cpp
754
+ +++ b/src/graphs/build_qwen35.cpp
755
+ @@ -153,7 +153,11 @@ struct ggml_tensor * llm_build_context::build_qwen35_mtp(
756
+
757
+ struct ggml_tensor * KQ_mask = build_inp_KQ_mask();
758
+
759
+ - struct ggml_tensor * inp_out_ids = (n_outputs < n_tokens) ? build_inp_out_ids() : nullptr;
760
+ + const bool mtp_cache_update_only =
761
+ + cparams.mtp_op_type == MTP_OP_WARMUP ||
762
+ + cparams.mtp_op_type == MTP_OP_UPDATE_ACCEPTED;
763
+ +
764
+ + struct ggml_tensor * inp_out_ids = (!mtp_cache_update_only && n_outputs < n_tokens) ? build_inp_out_ids() : nullptr;
765
+
766
+ ggml_tensor * token_emb = build_inp_embd_mtp(model.tok_embd);
767
+
768
+ @@ -210,11 +214,21 @@ struct ggml_tensor * llm_build_context::build_qwen35_mtp(
769
+ cur = lctx.cvec.apply_to(ctx0, cur, il);
770
+ cb(cur, "ffn_out", il);
771
+
772
+ + if (mtp_cache_update_only) {
773
+ + cb(cur, "result_mtp_cache_update", -1);
774
+ + return cur;
775
+ + }
776
+ +
777
+ cur = llm_build_norm(ctx0, cur, hparams, mtp_layer.nextn.shared_head_norm, NULL, LLM_NORM_RMS, cb, il);
778
+ cb(cur, "result_norm", -1);
779
+
780
+ cur = build_output(lctx, ctx0, cur, model.output, nullptr, cb);
781
+ cb(cur, "result_output", -1);
782
+
783
+ + if (lctx.mtp_draft_gpu_argmax && cparams.mtp_op_type == MTP_OP_DRAFT_GEN) {
784
+ + cur = ggml_argmax(ctx0, cur);
785
+ + cb(cur, "result_mtp_argmax", -1);
786
+ + }
787
+ +
788
+ return cur;
789
+ -}
790
+
791
+ +}
792
+ diff --git a/src/llama-context.h b/src/llama-context.h
793
+ index 7b6e56cf..d4f8ae19 100644
794
+ --- a/src/llama-context.h
795
+ +++ b/src/llama-context.h
796
+ @@ -264,6 +264,8 @@ struct llama_context {
797
+ void * abort_callback_data = nullptr;
798
+
799
+ const float * draft_input_hidden_state = nullptr;
800
+ + bool mtp_draft_gpu_argmax = false;
801
+ + std::vector<llama_token> mtp_draft_argmax;
802
+
803
+ // input tensors
804
+ struct ggml_tensor * inp_tokens; // I32 [n_batch]
805
+ @@ -289,6 +291,7 @@ struct llama_context {
806
+
807
+ struct Prev;
808
+ std::unique_ptr<Prev> prev;
809
+ + std::unique_ptr<Prev> prev_mtp;
810
+
811
+ void reset_scheduler();
812
+ bool can_reuse_graph(const llama_batch & u_batch);
813
+ diff --git a/src/llama-hparams.cpp b/src/llama-hparams.cpp
814
+ index 7053952c..cae099cb 100644
815
+ --- a/src/llama-hparams.cpp
816
+ +++ b/src/llama-hparams.cpp
817
+ @@ -36,6 +36,20 @@ static inline const char * llm_expert_gating_func_name(llm_expert_gating_func_ty
818
+ }
819
+ }
820
+
821
+ +static bool llm_detect_qwen35_recurrent_layer(const llama_model_loader & ml, uint32_t il, uint32_t fallback_interval) {
822
+ + const std::string ssm_name = "blk." + std::to_string(il) + ".ssm_conv1d.weight";
823
+ + if (ml.get_tensor_meta(ssm_name.c_str()) != nullptr) {
824
+ + return true;
825
+ + }
826
+ +
827
+ + const std::string attn_q_name = "blk." + std::to_string(il) + ".attn_q.weight";
828
+ + if (ml.get_tensor_meta(attn_q_name.c_str()) != nullptr) {
829
+ + return false;
830
+ + }
831
+ +
832
+ + return ((il + 1) % fallback_interval != 0);
833
+ +}
834
+ +
835
+
836
+ void llm_load_hparams(
837
+ llama_model_loader & ml,
838
+ @@ -507,7 +521,7 @@ void llm_load_hparams(
839
+ uint32_t full_attn_interval = 4;
840
+ ml.get_key(LLM_KV_FULL_ATTENTION_INTERVAL, full_attn_interval, false);
841
+ for (uint32_t i = 0; i < hparams.n_layer; ++i) {
842
+ - hparams.recurrent_layer_arr[i] = ((i + 1) % full_attn_interval != 0);
843
+ + hparams.recurrent_layer_arr[i] = llm_detect_qwen35_recurrent_layer(ml, i, full_attn_interval);
844
+ }
845
+ }
846
+
847
+ @@ -546,7 +560,7 @@ void llm_load_hparams(
848
+ const uint32_t n_main_layers = hparams.n_layer - hparams.nextn_predict_layers;
849
+ for (uint32_t i = 0; i < hparams.n_layer; ++i) {
850
+ if (i < n_main_layers) {
851
+ - hparams.recurrent_layer_arr[i] = ((i + 1) % full_attn_interval != 0);
852
+ + hparams.recurrent_layer_arr[i] = llm_detect_qwen35_recurrent_layer(ml, i, full_attn_interval);
853
+ } else {
854
+ hparams.recurrent_layer_arr[i] = false;
855
+ }
856
+ @@ -562,6 +576,10 @@ void llm_load_hparams(
857
+ model.type = hparams.n_embd == 2560 ? e_model::MODEL_4B : e_model::MODEL_9B; break;
858
+ case 64: // without MTP layer
859
+ case 65: // with MTP layer (64 main + 1 MTP)
860
+ + case 67: // RYS 11-14 without MTP layer
861
+ + case 68: // RYS 11-14 with MTP layer
862
+ + case 69: // RYS 15-20 without MTP layer
863
+ + case 70: // RYS 15-20 with MTP layer
864
+ model.type = e_model::MODEL_27B; break;
865
+ default: model.type = e_model::MODEL_UNKNOWN;
866
+ }
867
+ diff --git a/src/llama.cpp b/src/llama.cpp
868
+ index f7b55bbf..b8661462 100644
869
+ --- a/src/llama.cpp
870
+ +++ b/src/llama.cpp
871
+ @@ -548,36 +548,63 @@ struct llama_context::Prev {
872
+ int all_seq_id;
873
+ int n_outputs;
874
+ int n_kv;
875
+ + int n_tokens;
876
+ llama_mtp_op_type mtp_op_type;
877
+ + bool mtp_draft_gpu_argmax;
878
+ ggml_cgraph * graph;
879
+ };
880
+
881
+ void llama_context::reset_scheduler() {
882
+ ggml_backend_sched_reset(sched);
883
+ prev.reset();
884
+ + prev_mtp.reset();
885
+ }
886
+
887
+ bool llama_context::can_reuse_graph(const llama_batch & u_batch) {
888
+ - if (!prev || !prev->graph) return false;
889
+ - if (u_batch.n_tokens > 1) return false;
890
+ - if (u_batch.embd) return false;
891
+ if (!cparams.graph_reuse) return false;
892
+ - return u_batch.all_seq_id == prev->all_seq_id &&
893
+ + auto the_prev = cparams.mtp_op_type == MTP_OP_NONE ? prev.get() : prev_mtp.get();
894
+ + if (!the_prev || !the_prev->graph) return false;
895
+ + //if (u_batch.n_tokens > 1) return false;
896
+ + if (u_batch.embd) return false;
897
+ + return u_batch.all_seq_id == the_prev->all_seq_id &&
898
+ kv_self.head > 0 &&
899
+ - kv_self.n == prev->n_kv &&
900
+ - n_outputs == prev->n_outputs &&
901
+ - cparams.mtp_op_type == prev->mtp_op_type &&
902
+ + kv_self.n == the_prev->n_kv &&
903
+ + n_outputs == the_prev->n_outputs &&
904
+ + u_batch.n_tokens == the_prev->n_tokens &&
905
+ + cparams.mtp_op_type == the_prev->mtp_op_type &&
906
+ + mtp_draft_gpu_argmax == the_prev->mtp_draft_gpu_argmax &&
907
+ update_cache_copies();
908
+ }
909
+
910
+ +/*
911
+ +static void why_not_reuse_previous(const llama_batch & u_batch, const llama_context & ctx, const llama_context::Prev * the_prev) {
912
+ + if (!the_prev) { printf(" previous is null\n"); return; }
913
+ + if (!the_prev->graph) { printf(" previous graph is null\n"); return; }
914
+ + if (!ctx.cparams.graph_reuse) { printf(" graph_reuse is false\n"); return; }
915
+ + if (u_batch.embd) { printf(" ubatch.embd is not null\n"); return; }
916
+ + if (u_batch.all_seq_id != the_prev->all_seq_id) { printf(" all_seq_id is not the same\n"); return; }
917
+ + if (ctx.kv_self.head == 0) { printf(" kv_self.head = 0\n"); return; }
918
+ + if (ctx.kv_self.n != the_prev->n_kv) { printf(" kv_self.n is not the same\n"); return; }
919
+ + if (ctx.n_outputs != the_prev->n_outputs) { printf(" n_outputs is not the same\n"); return; }
920
+ + if (u_batch.n_tokens != the_prev->n_tokens) { printf(" n_tokens is not the same\n"); return; }
921
+ + if (ctx.cparams.mtp_op_type != the_prev->mtp_op_type) { printf(" mtp_op_type is not the same\n"); return; }
922
+ + printf(" update_cache_copies() must have failed\n");
923
+ +}
924
+ +*/
925
+ +
926
+ bool llama_context::update_cache_copies() {
927
+ - const int n_layer = model.mtp ? model.hparams.n_layer
928
+ - : model.hparams.n_layer - model.hparams.nextn_predict_layers; //cache_copies.size()/2;
929
+ + const int n_layer = model.mtp && cparams.mtp_op_type != MTP_OP_NONE ?
930
+ + model.hparams.n_layer : model.hparams.n_layer - model.hparams.nextn_predict_layers; //cache_copies.size()/2;
931
+ auto layer_has_attention_kv = [&](int il) {
932
+ return !model.hparams.is_recurrent(il);
933
+ };
934
+ - if ((int)kv_self.k_l.size() != n_layer) return false;
935
+ - if (!(kv_self.v_l.empty() || (int)kv_self.v_l.size() == n_layer)) return false;
936
+ +
937
+ + if ((int)kv_self.k_l.size() < n_layer) {
938
+ + return false;
939
+ + }
940
+ + if (!kv_self.v_l.empty() && (int)kv_self.v_l.size() < n_layer) {
941
+ + return false;
942
+ + }
943
+ for (int il = 0; il < n_layer; ++il) {
944
+ if (!layer_has_attention_kv(il) || kv_self.k_l[il] == nullptr) {
945
+ continue;
946
+ @@ -594,7 +621,9 @@ bool llama_context::update_cache_copies() {
947
+ for (int id = 0; id < kl->n_device; ++id) {
948
+ if (!kl->splits[id]) continue;
949
+ auto& c = cache_copies[2*model.splits.size()*il + 2*id + 0];
950
+ - if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != kl->splits[id]) return false;
951
+ + if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != kl->splits[id]) {
952
+ + return false;
953
+ + }
954
+ c.cpy->view_offs = kv_self.head*c.step;
955
+ c.cpy->src[1]->data = (char *)kl->splits[id]->data + c.cpy->view_offs;
956
+ c.cpy->data = c.cpy->src[1]->data;
957
+ @@ -603,29 +632,26 @@ bool llama_context::update_cache_copies() {
958
+ for (int id = 0; id < vl->n_device; ++id) {
959
+ if (!vl->splits[id]) continue;
960
+ auto& c = cache_copies[2*model.splits.size()*il + 2*id + 1];
961
+ - if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != vl->splits[id]) return false;
962
+ + if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != vl->splits[id]) {
963
+ + return false;
964
+ + }
965
+ c.cpy->view_offs = kv_self.head*c.step;
966
+ c.cpy->src[1]->data = (char *)vl->splits[id]->data + c.cpy->view_offs;
967
+ c.cpy->data = c.cpy->src[1]->data;
968
+ }
969
+ } else {
970
+ - for (int il = 0; il < n_layer; ++il) {
971
+ - if (!layer_has_attention_kv(il) || kv_self.k_l[il] == nullptr) {
972
+ - continue;
973
+ - }
974
+ - auto& c = cache_copies[2*il+0];
975
+ - if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != kv_self.k_l[il]) return false;
976
+ - c.cpy->view_offs = kv_self.head*c.step;
977
+ - c.cpy->src[1]->data = (char *)kv_self.k_l[il]->data + c.cpy->view_offs;
978
+ - c.cpy->data = c.cpy->src[1]->data;
979
+ + auto& c = cache_copies[2*il+0];
980
+ + if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != kv_self.k_l[il]) {
981
+ + return false;
982
+ }
983
+ - if (kv_self.v_l.empty()) return true;
984
+ - for (int il = 0; il < n_layer; ++il) {
985
+ - if (!layer_has_attention_kv(il) || kv_self.v_l[il] == nullptr) {
986
+ - continue;
987
+ - }
988
+ + c.cpy->view_offs = kv_self.head*c.step;
989
+ + c.cpy->src[1]->data = (char *)kv_self.k_l[il]->data + c.cpy->view_offs;
990
+ + c.cpy->data = c.cpy->src[1]->data;
991
+ + if (!kv_self.v_l.empty() && kv_self.v_l[il]) {
992
+ auto& c = cache_copies[2*il+1];
993
+ - if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != kv_self.v_l[il]) return false;
994
+ + if (!c.cpy || c.cpy->op != GGML_OP_CPY || c.cpy->view_src != kv_self.v_l[il]) {
995
+ + return false;
996
+ + }
997
+ c.cpy->view_offs = kv_self.head*c.step;
998
+ c.cpy->src[1]->data = (char *)kv_self.v_l[il]->data + c.cpy->view_offs;
999
+ c.cpy->data = c.cpy->src[1]->data;
1000
+ @@ -1516,7 +1542,7 @@ bool llama_kv_cache::per_step_alloc(int max_tokens) {
1001
+ }
1002
+
1003
+ bool llama_kv_cache::per_step_restore(int step) {
1004
+ - if (ckpt.per_step_ssm.empty() || step < 0) {
1005
+ + if (ckpt.per_step_ssm.empty() || step < 0 || step >= ckpt.per_step_max_allocated) {
1006
+ return false;
1007
+ }
1008
+
1009
+ @@ -1582,7 +1608,7 @@ bool llama_kv_cache::per_step_restore(int step) {
1010
+ n_restored++;
1011
+ }
1012
+
1013
+ - return true;
1014
+ + return n_restored > 0;
1015
+ }
1016
+
1017
+ static void llama_kv_cache_clear(struct llama_kv_cache & cache) {
1018
+ @@ -4084,6 +4110,27 @@ static void llama_set_inputs(llama_context & lctx, const llama_batch & batch) {
1019
+ static size_t llama_output_reserve(llama_context & lctx, size_t n_outputs) {
1020
+ const auto & cparams = lctx.cparams;
1021
+ const auto & hparams = lctx.model.hparams;
1022
+ + const bool has_mtp = lctx.model.hparams.nextn_predict_layers > 0 && lctx.cparams.mtp;
1023
+ + const bool mtp_cache_update_only =
1024
+ + has_mtp &&
1025
+ + (cparams.mtp_op_type == MTP_OP_WARMUP ||
1026
+ + cparams.mtp_op_type == MTP_OP_UPDATE_ACCEPTED);
1027
+ + if (mtp_cache_update_only && n_outputs == 0) {
1028
+ + lctx.mtp_draft_argmax.clear();
1029
+ + if (lctx.output_ids.empty()) {
1030
+ + lctx.output_ids.resize(cparams.n_batch);
1031
+ + }
1032
+ +
1033
+ + lctx.logits = nullptr;
1034
+ + lctx.embd = nullptr;
1035
+ + lctx.output_size = 0;
1036
+ + lctx.logits_size = 0;
1037
+ + lctx.embd_size = 0;
1038
+ + std::fill(lctx.output_ids.begin(), lctx.output_ids.end(), -1);
1039
+ + lctx.n_outputs = 0;
1040
+ +
1041
+ + return 0;
1042
+ + }
1043
+
1044
+ const size_t n_outputs_max = std::max(n_outputs, (size_t) cparams.n_seq_max);
1045
+
1046
+ @@ -4091,9 +4138,11 @@ static size_t llama_output_reserve(llama_context & lctx, size_t n_outputs) {
1047
+ const auto n_vocab = hparams.n_vocab;
1048
+ const auto n_embd = hparams.n_embd;
1049
+
1050
+ + const bool mtp_draft_gpu_argmax =
1051
+ + has_mtp && cparams.mtp_op_type == MTP_OP_DRAFT_GEN && lctx.mtp_draft_gpu_argmax;
1052
+ +
1053
+ // TODO: use a per-batch flag for logits presence instead
1054
+ - const bool has_mtp = lctx.model.hparams.nextn_predict_layers > 0 && lctx.cparams.mtp;
1055
+ - const bool has_logits = !cparams.embeddings || has_mtp;
1056
+ + const bool has_logits = !mtp_draft_gpu_argmax && (!cparams.embeddings || has_mtp);
1057
+ const bool has_embd = lctx.is_encoding || (cparams.embeddings && (cparams.pooling_type == LLAMA_POOLING_TYPE_NONE)) || has_mtp;
1058
+
1059
+ const size_t logits_size = has_logits ? n_vocab*n_outputs_max : 0;
1060
+ @@ -4140,6 +4189,12 @@ static size_t llama_output_reserve(llama_context & lctx, size_t n_outputs) {
1061
+ // set all ids as invalid (negative)
1062
+ std::fill(lctx.output_ids.begin(), lctx.output_ids.end(), -1);
1063
+
1064
+ + if (mtp_draft_gpu_argmax) {
1065
+ + lctx.mtp_draft_argmax.assign(n_outputs_max, LLAMA_TOKEN_NULL);
1066
+ + } else {
1067
+ + lctx.mtp_draft_argmax.clear();
1068
+ + }
1069
+ +
1070
+ if (has_mtp) {
1071
+ // MTP uses a large output footprint, clear only the active region.
1072
+ const size_t clear_size = (logits_size + embd_size) * sizeof(float);
1073
+ @@ -4254,9 +4309,16 @@ static int llama_decode_internal(
1074
+ // this indicates we are doing pooled embedding, so we ignore batch.logits and output all tokens
1075
+ const bool embd_pooled = cparams.embeddings && cparams.pooling_type != LLAMA_POOLING_TYPE_NONE;
1076
+ const bool has_mtp = cparams.mtp && hparams.nextn_predict_layers > 0;
1077
+ -
1078
+ + const bool mtp_cache_update_only =
1079
+ + has_mtp &&
1080
+ + (cparams.mtp_op_type == MTP_OP_WARMUP ||
1081
+ + cparams.mtp_op_type == MTP_OP_UPDATE_ACCEPTED);
1082
+ + const bool mtp_draft_gpu_argmax =
1083
+ + has_mtp && cparams.mtp_op_type == MTP_OP_DRAFT_GEN && lctx.mtp_draft_gpu_argmax;
1084
+ // count outputs
1085
+ - if (batch_all.logits && !embd_pooled) {
1086
+ + if (mtp_cache_update_only) {
1087
+ + n_outputs = 0;
1088
+ + } else if (batch_all.logits && !embd_pooled) {
1089
+ for (uint32_t i = 0; i < n_tokens_all; ++i) {
1090
+ n_outputs += batch_all.logits[i] != 0;
1091
+ }
1092
+ @@ -4268,7 +4330,7 @@ static int llama_decode_internal(
1093
+ }
1094
+
1095
+ // reserve output buffer
1096
+ - n_outputs_embd = has_mtp ? n_tokens_all : n_outputs;
1097
+ + n_outputs_embd = mtp_cache_update_only ? 0 : (has_mtp ? n_tokens_all : n_outputs);
1098
+ if (llama_output_reserve(lctx, std::max<size_t>(n_outputs, n_outputs_embd)) < std::max<size_t>(n_outputs, n_outputs_embd)) {
1099
+ LLAMA_LOG_ERROR("%s: could not reserve space for batch with %zu outputs\n", __func__, std::max<size_t>(n_outputs, n_outputs_embd));
1100
+ return -2;
1101
+ @@ -4357,7 +4419,9 @@ static int llama_decode_internal(
1102
+ {
1103
+ int32_t n_outputs_new = 0;
1104
+
1105
+ - if (u_batch.logits && !embd_pooled) {
1106
+ + if (mtp_cache_update_only) {
1107
+ + n_outputs_new = 0;
1108
+ + } else if (u_batch.logits && !embd_pooled) {
1109
+ for (uint32_t i = 0; i < n_tokens; i++) {
1110
+ n_outputs_new += u_batch.logits[i] != 0;
1111
+ }
1112
+ @@ -4438,21 +4502,15 @@ static int llama_decode_internal(
1113
+ printf("prelude(...): %d us\n", int(tim2-tim1));
1114
+ #endif
1115
+
1116
+ -
1117
+ - //if (n_tokens_all == 1) {
1118
+ - // printf("================= %s\n", __func__);
1119
+ - // printf(" all_pos_0 = %d, all_pos_1 = %d, all_seq_id = %d\n", batch_all.all_pos_0, batch_all.all_pos_1, batch_all.all_seq_id);
1120
+ - // printf(" embd = %p, logits = %p, token = %p\n", (const void *)batch_all.embd, (const void *)batch_all.logits, (const void *)batch_all.token);
1121
+ - // printf(" n_outputs = %d, kv_self.n = %d\n", n_outputs, kv_self.n);
1122
+ - //}
1123
+ - //printf("kv_self.n = %5d, kv_self.used = %5d, kv_self.head = %5d\n", kv_self.n, kv_self.used, kv_self.head);
1124
+ -
1125
+ #if IK_PRINT_TIMING
1126
+ tim1 = ggml_time_us();
1127
+ #endif
1128
+ + auto & prev = cparams.mtp_op_type == MTP_OP_NONE ? lctx.prev : lctx.prev_mtp;
1129
+ ggml_cgraph * gf = nullptr;
1130
+ if (!lctx.can_reuse_graph(u_batch)) {
1131
+ - lctx.reset_scheduler();
1132
+ + //lctx.reset_scheduler();
1133
+ + ggml_backend_sched_reset(lctx.sched);
1134
+ + prev.reset();
1135
+ ggml_backend_sched_set_eval_callback(lctx.sched, lctx.cparams.cb_eval, lctx.cparams.cb_eval_user_data);
1136
+ #if IK_PRINT_TIMING
1137
+ tim2 = ggml_time_us();
1138
+ @@ -4476,14 +4534,15 @@ static int llama_decode_internal(
1139
+ tim2 = ggml_time_us();
1140
+ printf("sched_alloc_graph(...): %d us\n", int(tim2-tim1));
1141
+ #endif
1142
+ - if (u_batch.n_tokens == 1 && u_batch.embd == nullptr && lctx.cparams.graph_reuse) {
1143
+ - lctx.prev = std::make_unique<llama_context::Prev>(llama_context::Prev{
1144
+ + //if (u_batch.n_tokens == 1 && u_batch.embd == nullptr && lctx.cparams.graph_reuse) {
1145
+ + if (u_batch.embd == nullptr && lctx.cparams.graph_reuse) {
1146
+ + prev = std::make_unique<llama_context::Prev>(llama_context::Prev{
1147
+ (int)u_batch.all_seq_id, (int)lctx.n_outputs, (int)lctx.kv_self.n,
1148
+ - cparams.mtp_op_type, gf});
1149
+ + (int)u_batch.n_tokens, cparams.mtp_op_type, lctx.mtp_draft_gpu_argmax, gf});
1150
+ }
1151
+ } else {
1152
+ - //printf("Reusing graph\n");
1153
+ - gf = lctx.prev->graph;
1154
+ + //printf("Reusing graph with n_kv = %d, n_tokens = %d\n", (int)prev->n_kv, (int)prev->n_tokens);
1155
+ + gf = prev->graph;
1156
+ }
1157
+
1158
+ if (cparams.mtp_op_type != MTP_OP_NONE) {
1159
+ @@ -4495,6 +4554,7 @@ static int llama_decode_internal(
1160
+ // the output is always the last tensor in the graph
1161
+ struct ggml_tensor * res = gf->nodes[gf->n_nodes - 1];
1162
+ struct ggml_tensor * embd = nullptr;
1163
+ + struct ggml_tensor * mtp_argmax = nullptr;
1164
+
1165
+ if (lctx.n_outputs == 0) {
1166
+ // no output
1167
+ @@ -4505,6 +4565,9 @@ static int llama_decode_internal(
1168
+ const bool use_qwen_mtp_embd = has_mtp && lctx.model.arch == LLM_ARCH_QWEN35;
1169
+ if (cparams.embeddings || has_mtp) {
1170
+ for (int i = gf->n_nodes - 1; i >= 0; --i) {
1171
+ + if (mtp_draft_gpu_argmax && strcmp(gf->nodes[i]->name, "result_mtp_argmax") == 0) {
1172
+ + mtp_argmax = gf->nodes[i];
1173
+ + }
1174
+ if (use_qwen_mtp_embd && strcmp(gf->nodes[i]->name, "result_mtp_embd") == 0) {
1175
+ // Qwen 3.5 uses raw hidden state before the final shared-head normalization.
1176
+ embd = gf->nodes[i];
1177
+ @@ -4526,6 +4589,10 @@ static int llama_decode_internal(
1178
+ GGML_ASSERT(strcmp(res->name, "result_output") == 0 && "missing result_output tensor");
1179
+ }
1180
+ }
1181
+ + if (mtp_draft_gpu_argmax) {
1182
+ + GGML_ASSERT(mtp_argmax != nullptr && "missing MTP draft argmax tensor");
1183
+ + res = nullptr;
1184
+ + }
1185
+ }
1186
+ // LLAMA_LOG_INFO("graph build time: %.3f ms (%d nodes, %d leafs)\n", (ggml_time_us() - t_start_us)/1000.0, gf->n_nodes, gf->n_leafs);
1187
+ #if IK_PRINT_TIMING == 1
1188
+ @@ -4566,12 +4633,26 @@ static int llama_decode_internal(
1189
+ // ggml_graph_dump_dot(gf, NULL, "llama.dot");
1190
+ //}
1191
+
1192
+ + if (mtp_argmax) {
1193
+ + ggml_backend_t backend_argmax = ggml_backend_sched_get_tensor_backend(lctx.sched, mtp_argmax);
1194
+ + GGML_ASSERT(backend_argmax != nullptr);
1195
+ +
1196
+ + const int32_t n_outputs_new = lctx.n_outputs;
1197
+ + if (n_outputs_new) {
1198
+ + GGML_ASSERT(n_outputs_prev + n_outputs_new <= n_outputs);
1199
+ + if (lctx.mtp_draft_argmax.size() < n_outputs) {
1200
+ + lctx.mtp_draft_argmax.resize(n_outputs, LLAMA_TOKEN_NULL);
1201
+ + }
1202
+ + llama_token * argmax_out = lctx.mtp_draft_argmax.data() + n_outputs_prev;
1203
+ + ggml_backend_tensor_get_async(backend_argmax, mtp_argmax, argmax_out, 0, n_outputs_new*sizeof(llama_token));
1204
+ + }
1205
+ + }
1206
+ +
1207
+ // extract logits
1208
+ if (res) {
1209
+ #if IK_PRINT_TIMING
1210
+ tim1 = ggml_time_us();
1211
+ #endif
1212
+ - // Do not process logits if MTP is only updating the KV cache.
1213
+ if (cparams.mtp_op_type != MTP_OP_WARMUP &&
1214
+ cparams.mtp_op_type != MTP_OP_UPDATE_ACCEPTED) {
1215
+ ggml_backend_t backend_res = ggml_backend_sched_get_tensor_backend(lctx.sched, res);
1216
+ @@ -4609,7 +4690,7 @@ static int llama_decode_internal(
1217
+ }
1218
+
1219
+ // extract embeddings
1220
+ - if (embd && (cparams.mtp_op_type == MTP_OP_NONE || cparams.mtp_op_type == MTP_OP_DRAFT_GEN)) {
1221
+ + if (embd && (cparams.mtp_op_type == MTP_OP_NONE || cparams.mtp_op_type == MTP_OP_DRAFT_GEN)) {
1222
+ #if IK_PRINT_TIMING
1223
+ tim1 = ggml_time_us();
1224
+ #endif
1225
+ @@ -6983,7 +7064,7 @@ bool llama_spec_ckpt_restore(struct llama_context * ctx, llama_seq_id seq_id,
1226
+ return false;
1227
+ }
1228
+ const llama_pos accepted_pos = n_past + accepted_step;
1229
+ - if (seq_id >= 0 && (uint32_t)seq_id < kv.size) {
1230
+ + if (kv.recurrent && seq_id >= 0 && (uint32_t)seq_id < kv.size) {
1231
+ kv.cells[seq_id].pos = accepted_pos;
1232
+ }
1233
+ llama_kv_cache_seq_rm(kv, seq_id, accepted_pos + 1, -1);
1234
+ @@ -6991,7 +7072,9 @@ bool llama_spec_ckpt_restore(struct llama_context * ctx, llama_seq_id seq_id,
1235
+ }
1236
+
1237
+ case LLAMA_SPEC_CKPT_GPU_FALLBACK:
1238
+ - kv.checkpoint_restore();
1239
+ + if (!kv.checkpoint_restore()) {
1240
+ + return false;
1241
+ + }
1242
+ llama_kv_cache_seq_rm(kv, seq_id, n_past, -1);
1243
+ return false;
1244
+
1245
+ @@ -8494,6 +8577,47 @@ void llama_set_mtp_op_type(llama_context * ctx, llama_mtp_op_type mtp_op_type) {
1246
+ ctx->set_mtp_op_type(mtp_op_type);
1247
+ }
1248
+
1249
+ +void llama_set_mtp_draft_gpu_argmax(struct llama_context * ctx, bool enabled) {
1250
+ + if (ctx->mtp_draft_gpu_argmax != enabled) {
1251
+ + ctx->mtp_draft_gpu_argmax = enabled;
1252
+ + ctx->prev_mtp.reset();
1253
+ + }
1254
+ +}
1255
+ +
1256
+ +llama_token llama_get_mtp_draft_argmax_ith(struct llama_context * ctx, int32_t i) {
1257
+ + int32_t j = -1;
1258
+ + llama_synchronize(ctx);
1259
+ +
1260
+ + try {
1261
+ + if (ctx->mtp_draft_argmax.empty()) {
1262
+ + throw std::runtime_error("no MTP draft argmax output");
1263
+ + }
1264
+ +
1265
+ + if (i < 0) {
1266
+ + j = ctx->n_outputs + i;
1267
+ + if (j < 0) {
1268
+ + throw std::runtime_error(format("negative index out of range [0, %d)", ctx->n_outputs));
1269
+ + }
1270
+ + } else if ((size_t) i >= ctx->output_ids.size()) {
1271
+ + throw std::runtime_error(format("out of range [0, %lu)", ctx->output_ids.size()));
1272
+ + } else {
1273
+ + j = ctx->output_ids[i];
1274
+ + }
1275
+ +
1276
+ + if (j < 0) {
1277
+ + throw std::runtime_error(format("batch.logits[%d] != true", i));
1278
+ + }
1279
+ + if (j >= ctx->n_outputs || (size_t) j >= ctx->mtp_draft_argmax.size()) {
1280
+ + throw std::runtime_error(format("corrupt MTP argmax buffer (j=%d, n_outputs=%d)", j, ctx->n_outputs));
1281
+ + }
1282
+ +
1283
+ + return ctx->mtp_draft_argmax[j];
1284
+ + } catch (const std::exception & err) {
1285
+ + LLAMA_LOG_ERROR("%s: invalid MTP argmax id %d, reason: %s\n", __func__, i, err.what());
1286
+ + return LLAMA_TOKEN_NULL;
1287
+ + }
1288
+ +}
1289
+ +
1290
+ void llama_synchronize(struct llama_context * ctx) {
1291
+ ggml_backend_sched_synchronize(ctx->sched);
1292
+