@miller-tech/uap 1.217.0 → 1.218.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1365 @@
1
+ diff --git a/common/sampling.cpp b/common/sampling.cpp
2
+ index 06dea1e1c..da8bdc1b1 100644
3
+ --- a/common/sampling.cpp
4
+ +++ b/common/sampling.cpp
5
+ @@ -4,6 +4,7 @@
6
+ #include "fit.h"
7
+ #include "log.h"
8
+ #include "reasoning-budget.h"
9
+ +#include "speculative.h"
10
+
11
+ #include "ggml.h"
12
+
13
+ @@ -12,6 +13,7 @@
14
+ #include <climits>
15
+ #include <cmath>
16
+ #include <cstring>
17
+ +#include <random>
18
+ #include <unordered_map>
19
+ #include <vector>
20
+
21
+ @@ -121,10 +123,14 @@ struct common_sampler {
22
+
23
+ llama_token_data_array cur_p;
24
+
25
+ + uint32_t speculative_seed;
26
+ + std::mt19937 speculative_rng;
27
+ +
28
+ void reset() {
29
+ prev.clear();
30
+
31
+ llama_sampler_reset(chain);
32
+ + speculative_rng.seed(speculative_seed);
33
+ }
34
+
35
+ void set_logits(struct llama_context * ctx, int idx) {
36
+ @@ -424,6 +430,8 @@ struct common_sampler * common_sampler_init(
37
+ params.backend_sampling = false;
38
+ }
39
+
40
+ + // Keep verifier randomness independent from both target and draft sampling.
41
+ + const uint32_t speculative_seed = llama_sampler_get_seed(chain) ^ 0x9e3779b9U;
42
+ auto * result = new common_sampler {
43
+ /* .params = */ params,
44
+ /* .grmr = */ grmr,
45
+ @@ -432,6 +440,8 @@ struct common_sampler * common_sampler_init(
46
+ /* .prev = */ ring_buffer<llama_token>(std::max(32, params.n_prev)),
47
+ /* .cur = */ {},
48
+ /* .cur_p = */ {},
49
+ + /* .speculative_seed = */ speculative_seed,
50
+ + /* .speculative_rng = */ std::mt19937(speculative_seed),
51
+ };
52
+
53
+ return result;
54
+ @@ -515,6 +525,8 @@ struct common_sampler * common_sampler_clone(common_sampler * gsmpl) {
55
+ /* .prev = */ gsmpl->prev,
56
+ /* .cur = */ gsmpl->cur,
57
+ /* .cur_p = */ gsmpl->cur_p,
58
+ + /* .speculative_seed = */ gsmpl->speculative_seed,
59
+ + /* .speculative_rng = */ gsmpl->speculative_rng,
60
+ };
61
+ }
62
+
63
+ @@ -535,6 +547,8 @@ void common_sampler_copy(const common_sampler * src, common_sampler * dst) {
64
+ dst->cur = src->cur;
65
+ dst->cur_p = src->cur_p;
66
+ dst->cur_p.data = src->cur_p.data ? dst->cur.data() : nullptr; // re-point to dst's buffer
67
+ + dst->speculative_seed = src->speculative_seed;
68
+ + dst->speculative_rng = src->speculative_rng;
69
+ dst->t_total_us = src->t_total_us;
70
+ }
71
+
72
+ @@ -705,6 +719,79 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
73
+ return result;
74
+ }
75
+
76
+ +std::vector<llama_token> common_sampler_sample_and_accept_n(
77
+ + struct common_sampler * gsmpl,
78
+ + struct llama_context * ctx,
79
+ + const std::vector<int> & idxs,
80
+ + const llama_tokens & draft,
81
+ + const std::vector<common_speculative_token_dist> & dists,
82
+ + bool grammar_first) {
83
+ + GGML_ASSERT(idxs.size() == draft.size() + 1);
84
+ + GGML_ASSERT(dists.size() == draft.size());
85
+ +
86
+ + std::vector<llama_token> result;
87
+ + result.reserve(idxs.size());
88
+ +
89
+ + std::uniform_real_distribution<float> uniform(0.0f, 1.0f);
90
+ + size_t i = 0;
91
+ + for (; i < draft.size(); ++i) {
92
+ + // Residual sampling needs the target distribution after every constraint.
93
+ + const llama_token fallback = common_sampler_sample(gsmpl, ctx, idxs[i], true);
94
+ + const auto & q = dists[i];
95
+ + GGML_ASSERT(q.ids.size() == q.probs.size());
96
+ +
97
+ + std::unordered_map<llama_token, float> q_probs;
98
+ + q_probs.reserve(q.ids.size());
99
+ + for (size_t j = 0; j < q.ids.size(); ++j) {
100
+ + q_probs[q.ids[j]] += q.probs[j];
101
+ + }
102
+ + const auto q_prob = [&](llama_token id) {
103
+ + const auto it = q_probs.find(id);
104
+ + return it == q_probs.end() ? 0.0f : it->second;
105
+ + };
106
+ +
107
+ + auto * p = common_sampler_get_candidates(gsmpl, false);
108
+ + float p_draft = 0.0f;
109
+ + const float q_draft = q_prob(draft[i]);
110
+ + for (size_t j = 0; j < p->size; ++j) {
111
+ + if (p->data[j].id == draft[i]) {
112
+ + p_draft = p->data[j].p;
113
+ + break;
114
+ + }
115
+ + }
116
+ +
117
+ + if (q_draft > 0.0f && uniform(gsmpl->speculative_rng) * q_draft <= p_draft) {
118
+ + common_sampler_accept(gsmpl, draft[i], true);
119
+ + result.push_back(draft[i]);
120
+ + continue;
121
+ + }
122
+ +
123
+ + std::vector<float> residual(p->size);
124
+ + float residual_sum = 0.0f;
125
+ + for (size_t j = 0; j < p->size; ++j) {
126
+ + residual[j] = std::max(0.0f, p->data[j].p - q_prob(p->data[j].id));
127
+ + residual_sum += residual[j];
128
+ + }
129
+ +
130
+ + llama_token id = fallback;
131
+ + if (residual_sum > 0.0f) {
132
+ + std::discrete_distribution<size_t> sample(residual.begin(), residual.end());
133
+ + id = p->data[sample(gsmpl->speculative_rng)].id;
134
+ + }
135
+ + common_sampler_accept(gsmpl, id, true);
136
+ + result.push_back(id);
137
+ + break;
138
+ + }
139
+ +
140
+ + if (i == draft.size()) {
141
+ + const llama_token id = common_sampler_sample(gsmpl, ctx, idxs[i], grammar_first);
142
+ + common_sampler_accept(gsmpl, id, true);
143
+ + result.push_back(id);
144
+ + }
145
+ +
146
+ + return result;
147
+ +}
148
+ +
149
+ 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) {
150
+ std::vector<int> idxs(draft.size() + 1);
151
+ for (size_t i = 0; i < idxs.size(); ++i) {
152
+ @@ -714,6 +801,19 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
153
+ return common_sampler_sample_and_accept_n(gsmpl, ctx, idxs, draft, grammar_first);
154
+ }
155
+
156
+ +std::vector<llama_token> common_sampler_sample_and_accept_n(
157
+ + struct common_sampler * gsmpl,
158
+ + struct llama_context * ctx,
159
+ + const llama_tokens & draft,
160
+ + const std::vector<common_speculative_token_dist> & dists,
161
+ + bool grammar_first) {
162
+ + std::vector<int> idxs(draft.size() + 1);
163
+ + for (size_t i = 0; i < idxs.size(); ++i) {
164
+ + idxs[i] = i;
165
+ + }
166
+ + return common_sampler_sample_and_accept_n(gsmpl, ctx, idxs, draft, dists, grammar_first);
167
+ +}
168
+ +
169
+ uint32_t common_sampler_get_seed(const struct common_sampler * gsmpl) {
170
+ return llama_sampler_get_seed(gsmpl->chain);
171
+ }
172
+ diff --git a/common/sampling.h b/common/sampling.h
173
+ index ced3c8364..5f3273eae 100644
174
+ --- a/common/sampling.h
175
+ +++ b/common/sampling.h
176
+ @@ -33,6 +33,7 @@
177
+ //
178
+
179
+ struct common_sampler;
180
+ +struct common_speculative_token_dist;
181
+
182
+ // llama_sampler API overloads
183
+
184
+ @@ -85,6 +86,23 @@ llama_token common_sampler_sample(struct common_sampler * gsmpl, struct llama_co
185
+ //
186
+ 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);
187
+
188
+ +// maximal-coupling verification for stochastic speculative decoding
189
+ +std::vector<llama_token> common_sampler_sample_and_accept_n(
190
+ + struct common_sampler * gsmpl,
191
+ + struct llama_context * ctx,
192
+ + const std::vector<int> & idxs,
193
+ + const llama_tokens & draft,
194
+ + const std::vector<common_speculative_token_dist> & dists,
195
+ + bool grammar_first = false);
196
+ +
197
+ +// assume idxs == [ 0, 1, 2, ..., draft.size() ]
198
+ +std::vector<llama_token> common_sampler_sample_and_accept_n(
199
+ + struct common_sampler * gsmpl,
200
+ + struct llama_context * ctx,
201
+ + const llama_tokens & draft,
202
+ + const std::vector<common_speculative_token_dist> & dists,
203
+ + bool grammar_first = false);
204
+ +
205
+ // assume idxs == [ 0, 1, 2, ..., draft.size() ]
206
+ 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);
207
+
208
+ diff --git a/common/speculative.cpp b/common/speculative.cpp
209
+ index 89e9b2782..78bb4a6d5 100644
210
+ --- a/common/speculative.cpp
211
+ +++ b/common/speculative.cpp
212
+ @@ -14,10 +14,12 @@
213
+
214
+ #include <algorithm>
215
+ #include <cassert>
216
+ +#include <chrono>
217
+ +#include <cinttypes>
218
+ #include <cstring>
219
+ #include <iomanip>
220
+ #include <map>
221
+ -#include <cinttypes>
222
+ +#include <random>
223
+
224
+ #define SPC_DBG(fmt, ...) LOG_DBG("spec %12.*s: " fmt, 12, __func__, __VA_ARGS__)
225
+ #define SPC_TRC(fmt, ...) LOG_TRC("spec %12.*s: " fmt, 12, __func__, __VA_ARGS__)
226
+ @@ -923,6 +925,11 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
227
+ int32_t block_size = 0;
228
+ llama_token mask_token_id = 0;
229
+
230
+ + bool is_dflash2 = false;
231
+ + int32_t selector_top_k = 0;
232
+ + std::vector<std::mt19937> selector_rng;
233
+ + std::vector<bool> selector_reset;
234
+ +
235
+ // draft-dspark: the draft carries a Markov head and uses an anchor-first block layout
236
+ const bool is_dspark;
237
+
238
+ @@ -966,6 +973,10 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
239
+ if (llama_model_meta_val_str(model_dft, "dflash.sample_from_anchor", buf, sizeof(buf)) >= 0) {
240
+ sample_from_anchor = std::strcmp(buf, "true") == 0;
241
+ }
242
+ + if (llama_model_meta_val_str(model_dft, "dflash.selector_top_k", buf, sizeof(buf)) >= 0) {
243
+ + selector_top_k = std::atoi(buf);
244
+ + is_dflash2 = selector_top_k > 0;
245
+ + }
246
+ }
247
+ mask_token_id = llama_vocab_mask(llama_model_get_vocab(model_dft));
248
+
249
+ @@ -991,14 +1002,17 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
250
+ for (auto & s : smpls) {
251
+ common_params_sampling sparams;
252
+ sparams.no_perf = false;
253
+ - sparams.top_k = 10;
254
+ + sparams.top_k = is_dflash2 ? selector_top_k : 10;
255
+ sparams.samplers = { COMMON_SAMPLER_TYPE_TOP_K };
256
+ s.reset(common_sampler_init(model_dft, sparams));
257
+ }
258
+
259
+ + selector_rng.resize(n_seq);
260
+ + selector_reset.assign(n_seq, true);
261
+ +
262
+ // offload draft sampling to the backend
263
+ backend_chains.assign(n_seq, nullptr);
264
+ - if (this->params.backend_sampling) {
265
+ + if (this->params.backend_sampling && !is_dflash2) {
266
+ for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
267
+ llama_sampler * chain = llama_sampler_chain_init(llama_sampler_chain_default_params());
268
+ llama_sampler_chain_add(chain, llama_sampler_init_top_k(10));
269
+ @@ -1017,7 +1031,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
270
+ llama_set_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k], true);
271
+ }
272
+
273
+ - llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ true);
274
+ + // DFlash2 reads its selector lattice from h_nextn and never consumes raw logits.
275
+ + llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ !is_dflash2);
276
+ llama_set_causal_attn(ctx_dft, false); // DFlash needs non-causal attention
277
+ }
278
+
279
+ @@ -1048,6 +1063,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
280
+ return;
281
+ }
282
+
283
+ + selector_reset[seq_id] = true;
284
+ +
285
+ const llama_pos pos_max = llama_memory_seq_pos_max(llama_get_memory(params.ctx_dft), seq_id);
286
+ if (pos_max < N - 1) {
287
+ LOG_WRN("%s: ctx_dft pos_max=%d < N-1=%d - process() did not run on every prefill ubatch. "
288
+ @@ -1074,87 +1091,68 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
289
+
290
+ const int32_t n_tokens = batch_in.n_tokens;
291
+
292
+ - // per-seq inclusive batch range (assumes each seq's tokens are contiguous in the batch)
293
+ - std::vector<int32_t> i_batch_beg(n_seq, -1);
294
+ - std::vector<int32_t> i_batch_end(n_seq, -1);
295
+ - for (int32_t k = 0; k < n_tokens; ++k) {
296
+ - GGML_ASSERT(batch_in.n_seq_id[k] == 1);
297
+ - const llama_seq_id seq_id = batch_in.seq_id[k][0];
298
+ - if (seq_id < 0 || seq_id >= (llama_seq_id) n_seq) {
299
+ - continue;
300
+ - }
301
+ - i_batch_end[seq_id] = k;
302
+ - if (i_batch_beg[seq_id] < 0) {
303
+ - i_batch_beg[seq_id] = k;
304
+ - }
305
+ - }
306
+ -
307
+ auto * ctx_tgt = this->params.ctx_tgt;
308
+ auto * ctx_dft = this->params.ctx_dft;
309
+
310
+ const int32_t n_ubatch = (int32_t) llama_n_ubatch(ctx_dft);
311
+
312
+ - for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
313
+ - if (i_batch_beg[seq_id] < 0) {
314
+ - continue;
315
+ - }
316
+ - const int32_t n_rows = i_batch_end[seq_id] - i_batch_beg[seq_id] + 1;
317
+ -
318
+ - for (int32_t offset = 0; offset < n_rows; offset += n_ubatch) {
319
+ - const int32_t n_chunk = std::min(n_ubatch, n_rows - offset);
320
+ -
321
+ - // gather this chunk's target features, interleaved by extract layer
322
+ - features_buf.resize((size_t) n_chunk * n_embd_enc);
323
+ - for (uint32_t k = 0; k < target_layer_ids_n; ++k) {
324
+ - const float * layer = llama_get_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k]);
325
+ - if (!layer) {
326
+ - GGML_ABORT("DFlash: target layer %d input not extracted.", target_layer_ids[k]);
327
+ - }
328
+ - for (int32_t i = 0; i < n_chunk; ++i) {
329
+ - float * dst = features_buf.data() + (size_t) i * n_embd_enc + k * (size_t) n_embd_tgt;
330
+ - const float * src = layer + (size_t) (i_batch_beg[seq_id] + offset + i) * n_embd_tgt;
331
+ - std::memcpy(dst, src, (size_t) n_embd_tgt * sizeof(float));
332
+ - }
333
+ + // Flatten token-wise encoder work into shared chunks while preserving each row's position and sequence.
334
+ + for (int32_t offset = 0; offset < n_tokens; offset += n_ubatch) {
335
+ + const int32_t n_chunk = std::min(n_ubatch, n_tokens - offset);
336
+ + features_buf.resize((size_t) n_chunk * n_embd_enc);
337
+ + for (uint32_t k = 0; k < target_layer_ids_n; ++k) {
338
+ + const float * layer = llama_get_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k]);
339
+ + if (!layer) {
340
+ + GGML_ABORT("DFlash: target layer %d input not extracted.", target_layer_ids[k]);
341
+ + }
342
+ + for (int32_t i = 0; i < n_chunk; ++i) {
343
+ + float * dst = features_buf.data() + (size_t) i * n_embd_enc + k * (size_t) n_embd_tgt;
344
+ + const float * src = layer + (size_t) (offset + i) * n_embd_tgt;
345
+ + std::memcpy(dst, src, (size_t) n_embd_tgt * sizeof(float));
346
+ }
347
+ + }
348
+
349
+ - // fuse extracted features through DFlash encoder
350
+ - llama_batch enc_batch = {
351
+ - /*.n_tokens =*/ n_chunk,
352
+ - /*.token =*/ nullptr,
353
+ - /*.embd =*/ features_buf.data(),
354
+ - /*.pos =*/ nullptr,
355
+ - /*.n_seq_id =*/ nullptr,
356
+ - /*.seq_id =*/ nullptr,
357
+ - /*.logits =*/ nullptr,
358
+ - };
359
+ + llama_batch enc_batch = {
360
+ + /*.n_tokens =*/ n_chunk,
361
+ + /*.token =*/ nullptr,
362
+ + /*.embd =*/ features_buf.data(),
363
+ + /*.pos =*/ nullptr,
364
+ + /*.n_seq_id =*/ nullptr,
365
+ + /*.seq_id =*/ nullptr,
366
+ + /*.logits =*/ nullptr,
367
+ + };
368
+
369
+ - int32_t rc = llama_encode(ctx_dft, enc_batch);
370
+ - if (rc != 0) {
371
+ - LOG_ERR("%s: llama_encode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
372
+ - __func__, rc, (int) n_chunk, (int) offset);
373
+ - return false;
374
+ - }
375
+ + int32_t rc = llama_encode(ctx_dft, enc_batch);
376
+ + if (rc != 0) {
377
+ + LOG_ERR("%s: llama_encode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
378
+ + __func__, rc, (int) n_chunk, (int) offset);
379
+ + return false;
380
+ + }
381
+
382
+ - const float * inp_g = llama_get_embeddings_nextn(ctx_dft);
383
+ - GGML_ASSERT(inp_g && "DFlash encoder produced no output.");
384
+ + const float * inp_g = llama_get_embeddings_nextn(ctx_dft);
385
+ + GGML_ASSERT(inp_g && "DFlash encoder produced no output.");
386
+
387
+ - // inject the DFlash decoder K/V cache at the tokens' target positions
388
+ - batch_inject.n_tokens = n_chunk;
389
+ - std::memcpy(batch_inject.embd, inp_g, (size_t) n_chunk * n_embd_dec * sizeof(float));
390
+ + batch_inject.n_tokens = n_chunk;
391
+ + std::memcpy(batch_inject.embd, inp_g, (size_t) n_chunk * n_embd_dec * sizeof(float));
392
+ + for (int32_t i = 0; i < n_chunk; ++i) {
393
+ + const int32_t j = offset + i;
394
+ + GGML_ASSERT(batch_in.n_seq_id[j] == 1);
395
+ + const llama_seq_id seq_id = batch_in.seq_id[j][0];
396
+ + GGML_ASSERT(seq_id >= 0 && seq_id < (llama_seq_id) n_seq);
397
+ + batch_inject.pos[i] = batch_in.pos[j];
398
+ + batch_inject.n_seq_id[i] = 1;
399
+ + batch_inject.seq_id[i][0] = seq_id;
400
+ + batch_inject.logits[i] = false;
401
+ + }
402
+
403
+ - for (int32_t i = 0; i < n_chunk; ++i) {
404
+ - batch_inject.pos[i] = batch_in.pos[i_batch_beg[seq_id] + offset + i];
405
+ - batch_inject.n_seq_id[i] = 1;
406
+ - batch_inject.seq_id[i][0] = seq_id;
407
+ - batch_inject.logits[i] = false;
408
+ - }
409
+ - rc = llama_decode(ctx_dft, batch_inject);
410
+ - if (rc != 0) {
411
+ - LOG_ERR("%s: llama_decode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
412
+ - __func__, rc, (int) n_chunk, (int) offset);
413
+ - return false;
414
+ - }
415
+ + rc = llama_decode(ctx_dft, batch_inject);
416
+ + if (rc != 0) {
417
+ + LOG_ERR("%s: llama_decode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
418
+ + __func__, rc, (int) n_chunk, (int) offset);
419
+ + return false;
420
+ }
421
+ + // The server may switch contexts before the next draft decode.
422
+ + llama_synchronize(ctx_dft);
423
+ }
424
+
425
+ return true;
426
+ @@ -1186,7 +1184,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
427
+ i_block_beg[seq_id] = batch.n_tokens;
428
+ n_block [seq_id] = n_block_tokens;
429
+ for (int32_t i = 0; i < n_block_tokens; ++i) {
430
+ - common_batch_add(batch, i == 0 ? dp.id_last : mask_token_id, n + i, { seq_id }, true);
431
+ + common_batch_add(batch, i == 0 ? dp.id_last : mask_token_id, n + i, { seq_id }, !is_dflash2);
432
+ }
433
+ }
434
+
435
+ @@ -1214,6 +1212,63 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
436
+
437
+ auto & result = *dp.result;
438
+
439
+ + if (dp.dists) {
440
+ + dp.dists->clear();
441
+ + }
442
+ +
443
+ + if (is_dflash2) {
444
+ + GGML_ASSERT(dp.temperature <= 0.0f || dp.dists);
445
+ + const float * lattice = llama_get_embeddings_nextn(ctx_dft);
446
+ + GGML_ASSERT(lattice && "DFlash2 selector produced no lattice");
447
+ +
448
+ + if (selector_reset[seq_id]) {
449
+ + uint32_t seed = dp.seed;
450
+ + if (seed == LLAMA_DEFAULT_SEED) {
451
+ + seed = (uint32_t) std::chrono::high_resolution_clock::now().time_since_epoch().count();
452
+ + }
453
+ + selector_rng[seq_id].seed(seed ^ 0x85ebca6bU);
454
+ + selector_reset[seq_id] = false;
455
+ + }
456
+ +
457
+ + int32_t predecessor = 0;
458
+ + for (int32_t i = 1; i < n_block_tokens; ++i) {
459
+ + const float * row = lattice + (size_t) (beg + i) * n_embd_dec;
460
+ + const float * scores = row + selector_top_k + (size_t) predecessor * selector_top_k;
461
+ +
462
+ + if (dp.temperature > 0.0f) {
463
+ + common_speculative_token_dist dist;
464
+ + dist.ids.resize(selector_top_k);
465
+ + dist.probs.resize(selector_top_k);
466
+ + const float max_score = *std::max_element(scores, scores + selector_top_k);
467
+ + float sum = 0.0f;
468
+ + for (int32_t k = 0; k < selector_top_k; ++k) {
469
+ + dist.ids[k] = (llama_token) row[k];
470
+ + dist.probs[k] = std::exp((scores[k] - max_score) / dp.temperature);
471
+ + sum += dist.probs[k];
472
+ + }
473
+ + for (float & p : dist.probs) {
474
+ + p /= sum;
475
+ + }
476
+ + std::discrete_distribution<int32_t> sample(dist.probs.begin(), dist.probs.end());
477
+ + predecessor = sample(selector_rng[seq_id]);
478
+ + result.push_back(dist.ids[predecessor]);
479
+ + dp.dists->push_back(std::move(dist));
480
+ + } else {
481
+ + predecessor = (int32_t) std::distance(scores,
482
+ + std::max_element(scores, scores + selector_top_k));
483
+ + result.push_back((llama_token) row[predecessor]);
484
+ + }
485
+ + }
486
+ +
487
+ + if (result.size() < (size_t) params.n_min) {
488
+ + result.clear();
489
+ + if (dp.dists) {
490
+ + dp.dists->clear();
491
+ + }
492
+ + }
493
+ + continue;
494
+ + }
495
+ +
496
+ if (is_dspark) {
497
+ // DSpark: read from the first draft slot, truncate below the confidence threshold
498
+ const float * conf = params.p_min > 0.0f ? llama_get_embeddings_nextn(ctx_dft) : nullptr;
499
+ @@ -2339,7 +2394,7 @@ common_params common_base_params_to_speculative(const common_params & params) {
500
+ result.n_outputs_max = params.n_parallel;
501
+ result.n_outputs_max_per_seq = 1;
502
+
503
+ - // dflash/dspark decode the whole noise block in a single pass and sample every block position on the backend
504
+ + // dflash/dspark decode every sequence's full noise block in one pass
505
+ // TODO: refactor such properties to be announced by the speculative types
506
+ // something like `struct common_speculative_type_props common_speculative_type_get_props(...);`
507
+ const bool has_block_draft = std::any_of(
508
+ @@ -2351,6 +2406,8 @@ common_params common_base_params_to_speculative(const common_params & params) {
509
+ // per-seq output positions: DFlash decodes anchor + n_max masks (n_max + 1); DSpark n_max -> +1 covers both
510
+ const int32_t per_seq = std::max(1, params_spec.n_max + 1);
511
+ result.n_outputs_max = params.n_parallel * per_seq;
512
+ + result.n_batch = std::max(result.n_batch, result.n_outputs_max);
513
+ + result.n_ubatch = std::max(result.n_ubatch, result.n_outputs_max);
514
+ if (params_spec.backend_sampling) {
515
+ result.n_outputs_max_per_seq = per_seq;
516
+ }
517
+ diff --git a/common/speculative.h b/common/speculative.h
518
+ index 12ae31b7d..f7dd65267 100644
519
+ --- a/common/speculative.h
520
+ +++ b/common/speculative.h
521
+ @@ -5,6 +5,11 @@
522
+
523
+ struct common_speculative;
524
+
525
+ +struct common_speculative_token_dist {
526
+ + llama_tokens ids;
527
+ + std::vector<float> probs;
528
+ +};
529
+ +
530
+ // comma separated list the provided types
531
+ std::string common_speculative_type_name_str(const std::vector<enum common_speculative_type> & types);
532
+
533
+ @@ -60,6 +65,12 @@ struct common_speculative_draft_params {
534
+
535
+ // the generated draft from the last _draft() call
536
+ llama_tokens * result;
537
+ +
538
+ + // optional sparse proposal distributions, one per draft token
539
+ + std::vector<common_speculative_token_dist> * dists = nullptr;
540
+ +
541
+ + float temperature = 0.0f;
542
+ + uint32_t seed = LLAMA_DEFAULT_SEED;
543
+ };
544
+
545
+ common_speculative_draft_params & common_speculative_get_draft_params(common_speculative * spec, llama_seq_id seq_id);
546
+ diff --git a/conversion/__init__.py b/conversion/__init__.py
547
+ index 5ae6ad819..5d303b4a3 100644
548
+ --- a/conversion/__init__.py
549
+ +++ b/conversion/__init__.py
550
+ @@ -54,6 +54,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
551
+ "DeepseekV3ForCausalLM": "deepseek",
552
+ "DeepseekV32ForCausalLM": "deepseek",
553
+ "DFlashDraftModel": "qwen",
554
+ + "DFlash2DraftModel": "qwen",
555
+ "Qwen3DSparkModel": "qwen",
556
+ "DSparkDraftModel": "qwen",
557
+ "DSparkSpeculator": "qwen",
558
+ diff --git a/conversion/qwen.py b/conversion/qwen.py
559
+ index 26b10452b..ab2a0fb4d 100644
560
+ --- a/conversion/qwen.py
561
+ +++ b/conversion/qwen.py
562
+ @@ -639,7 +639,7 @@ class Qwen3_5MoeTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
563
+ model_arch = gguf.MODEL_ARCH.QWEN35MOE
564
+
565
+
566
+ -@ModelBase.register("DFlashDraftModel")
567
+ +@ModelBase.register("DFlashDraftModel", "DFlash2DraftModel")
568
+ @ModelBase.example("z-lab/Qwen3.5-9B-DFlash")
569
+ class DFlashModel(Qwen3Model):
570
+ model_arch = gguf.MODEL_ARCH.DFLASH
571
+ @@ -678,9 +678,31 @@ class DFlashModel(Qwen3Model):
572
+ def set_gguf_parameters(self):
573
+ super().set_gguf_parameters()
574
+
575
+ - block_size = self.hparams.get("block_size", 16)
576
+ - self.gguf_writer.add_block_size(block_size)
577
+ dflash_config = self.hparams.get("dflash_config", {})
578
+ + block_size = dflash_config.get("block_size", self.hparams.get("block_size", 16))
579
+ + self.gguf_writer.add_block_size(block_size)
580
+ +
581
+ + if "conv_kernel_size" in dflash_config:
582
+ + self.gguf_writer.add_conv_kernel_size(int(dflash_config["conv_kernel_size"]))
583
+ + self.gguf_writer.add_conv_group_size(int(dflash_config["conv_group_size"]))
584
+ + self.gguf_writer.add_selector_rank(int(dflash_config["selector_rank"]))
585
+ + self.gguf_writer.add_selector_top_k(int(dflash_config["selector_top_k"]))
586
+ +
587
+ + output_multiplier = dflash_config.get(
588
+ + "output_multiplier", self.hparams.get("output_multiplier")
589
+ + )
590
+ + if output_multiplier is not None:
591
+ + self.gguf_writer.add_logit_scale(float(output_multiplier))
592
+ + softcap = dflash_config.get(
593
+ + "final_logit_softcapping", self.hparams.get("final_logit_softcapping")
594
+ + )
595
+ + if softcap is not None and float(softcap) > 0:
596
+ + self.gguf_writer.add_final_logit_softcapping(float(softcap))
597
+ + embedding_scale = dflash_config.get(
598
+ + "input_embedding_scale", self.hparams.get("input_embedding_scale")
599
+ + )
600
+ + if embedding_scale is not None:
601
+ + self.gguf_writer.add_embedding_scale(float(embedding_scale))
602
+
603
+ target_layer_ids = dflash_config.get("target_layer_ids", [])
604
+ if target_layer_ids:
605
+ @@ -706,6 +728,12 @@ class DFlashModel(Qwen3Model):
606
+ if name == "model.embed_tokens.weight" and not self.hparams.get("has_embed_tokens", True):
607
+ return
608
+
609
+ + if name in (
610
+ + "model.candidate_selector.predecessor_codebook",
611
+ + "model.candidate_selector.successor_codebook",
612
+ + ):
613
+ + name += ".weight"
614
+ +
615
+ yield from super().modify_tensors(data_torch, name, bid)
616
+
617
+
618
+ diff --git a/examples/speculative-simple/speculative-simple.cpp b/examples/speculative-simple/speculative-simple.cpp
619
+ index 487ae03ab..d074f270f 100644
620
+ --- a/examples/speculative-simple/speculative-simple.cpp
621
+ +++ b/examples/speculative-simple/speculative-simple.cpp
622
+ @@ -66,7 +66,8 @@ int main(int argc, char ** argv) {
623
+ llama_context * ctx_dft = params.speculative.draft.ctx_dft;
624
+
625
+ // check if the context supports partial sequence removal
626
+ - const bool use_ckpt_tgt = common_context_can_seq_rm(ctx_tgt) == COMMON_CONTEXT_SEQ_RM_TYPE_FULL;
627
+ + const auto ctx_tgt_seq_rm_type = common_context_can_seq_rm(ctx_tgt);
628
+ + const bool use_ckpt_tgt = ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_FULL;
629
+ const bool use_ckpt_dft = common_context_can_seq_rm(ctx_dft) == COMMON_CONTEXT_SEQ_RM_TYPE_FULL;
630
+
631
+ if (use_ckpt_tgt) {
632
+ @@ -152,6 +153,7 @@ int main(int argc, char ** argv) {
633
+ llama_batch batch_tgt = llama_batch_init(llama_n_batch(ctx_tgt), 0, 1);
634
+
635
+ llama_tokens draft;
636
+ + std::vector<common_speculative_token_dist> dists;
637
+
638
+ common_prompt_checkpoint ckpt;
639
+
640
+ @@ -192,6 +194,9 @@ int main(int argc, char ** argv) {
641
+ /* .id_last = */ id_last,
642
+ /* .prompt = */ &prompt_tgt,
643
+ /* .result = */ &draft, // output
644
+ + /* .dists = */ &dists,
645
+ + /* .temperature = */ params.sampling.temp,
646
+ + /* .seed = */ common_sampler_get_seed(smpl.get()),
647
+ };
648
+ common_speculative_draft(spec);
649
+
650
+ @@ -255,7 +260,12 @@ int main(int argc, char ** argv) {
651
+ // available logits from the batch and sample the next token until we run out of logits or the sampler
652
+ // disagrees with the draft
653
+ //
654
+ - auto ids = common_sampler_sample_and_accept_n(smpl.get(), ctx_tgt, draft);
655
+ + const bool can_rollback =
656
+ + ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_PART ||
657
+ + (ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_RS && draft.size() <= llama_n_rs_seq(ctx_tgt));
658
+ + auto ids = can_rollback && params.sampling.temp > 0.0f && dists.size() == draft.size()
659
+ + ? common_sampler_sample_and_accept_n(smpl.get(), ctx_tgt, draft, dists)
660
+ + : common_sampler_sample_and_accept_n(smpl.get(), ctx_tgt, draft);
661
+
662
+ //LOG_DBG("ids: %s\n", string_from(ctx_tgt, ids).c_str());
663
+
664
+ @@ -268,6 +278,7 @@ int main(int argc, char ** argv) {
665
+ LOG_DBG("partial acceptance: %zu < %zu, restoring checkpoint\n", ids.size() - 1, n_draft);
666
+
667
+ draft = std::move(ids);
668
+ + dists.clear();
669
+
670
+ {
671
+ ckpt.load_tgt(ctx_tgt, seq_id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
672
+ @@ -325,6 +336,7 @@ int main(int argc, char ** argv) {
673
+
674
+ // clear the draft since it has been consumed
675
+ draft.clear();
676
+ + dists.clear();
677
+
678
+ {
679
+ LOG_DBG("clear kv cache from any extra tokens, n_past = %d\n", n_past);
680
+ diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py
681
+ index fad8d1fd8..3c81675c5 100644
682
+ --- a/gguf-py/gguf/constants.py
683
+ +++ b/gguf-py/gguf/constants.py
684
+ @@ -162,6 +162,10 @@ class Keys:
685
+ TARGET_LAYERS = "{arch}.target_layers"
686
+ TARGET_HIDDEN_SIZE = "{arch}.target_hidden_size"
687
+ BLOCK_SIZE = "{arch}.block_size"
688
+ + CONV_KERNEL_SIZE = "{arch}.conv_kernel_size"
689
+ + CONV_GROUP_SIZE = "{arch}.conv_group_size"
690
+ + SELECTOR_RANK = "{arch}.selector_rank"
691
+ + SELECTOR_TOP_K = "{arch}.selector_top_k"
692
+ SAMPLE_FROM_ANCHOR = "{arch}.sample_from_anchor"
693
+ NORM_BEFORE_RESIDUAL = "{arch}.norm_before_residual"
694
+ NORM_BEFORE_FC = "{arch}.norm_before_fc"
695
+ @@ -1135,6 +1139,13 @@ class MODEL_TENSOR(IntEnum):
696
+ DSPARK_MARKOV_W1 = auto() # markov head: prev-token embed
697
+ DSPARK_MARKOV_W2 = auto() # markov head: bias projection
698
+ DSPARK_CONF_PROJ = auto() # confidence head
699
+ + DFLASH_ATTN_CONV_BASE = auto()
700
+ + DFLASH_ATTN_CONV_PROJ = auto()
701
+ + DFLASH_FFN_CONV_BASE = auto()
702
+ + DFLASH_FFN_CONV_PROJ = auto()
703
+ + DFLASH_SELECTOR_PREV = auto()
704
+ + DFLASH_SELECTOR_NEXT = auto()
705
+ + DFLASH_SELECTOR_HIDDEN = auto()
706
+ # lfm2 audio
707
+ A_ENC_NORM_CONV = auto()
708
+ A_ENC_LINEAR_POS = auto()
709
+ @@ -1878,6 +1889,13 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
710
+ MODEL_TENSOR.DSPARK_MARKOV_W1: "markov_w1",
711
+ MODEL_TENSOR.DSPARK_MARKOV_W2: "markov_w2",
712
+ MODEL_TENSOR.DSPARK_CONF_PROJ: "conf_proj",
713
+ + MODEL_TENSOR.DFLASH_ATTN_CONV_BASE: "blk.{bid}.attn_conv_base",
714
+ + MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ: "blk.{bid}.attn_conv_proj",
715
+ + MODEL_TENSOR.DFLASH_FFN_CONV_BASE: "blk.{bid}.ffn_conv_base",
716
+ + MODEL_TENSOR.DFLASH_FFN_CONV_PROJ: "blk.{bid}.ffn_conv_proj",
717
+ + MODEL_TENSOR.DFLASH_SELECTOR_PREV: "selector_predecessor",
718
+ + MODEL_TENSOR.DFLASH_SELECTOR_NEXT: "selector_successor",
719
+ + MODEL_TENSOR.DFLASH_SELECTOR_HIDDEN: "selector_hidden",
720
+ MODEL_TENSOR.D2T: "d2t",
721
+ }
722
+
723
+ @@ -4893,6 +4911,13 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
724
+ MODEL_TENSOR.DSPARK_MARKOV_W1,
725
+ MODEL_TENSOR.DSPARK_MARKOV_W2,
726
+ MODEL_TENSOR.DSPARK_CONF_PROJ,
727
+ + MODEL_TENSOR.DFLASH_ATTN_CONV_BASE,
728
+ + MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ,
729
+ + MODEL_TENSOR.DFLASH_FFN_CONV_BASE,
730
+ + MODEL_TENSOR.DFLASH_FFN_CONV_PROJ,
731
+ + MODEL_TENSOR.DFLASH_SELECTOR_PREV,
732
+ + MODEL_TENSOR.DFLASH_SELECTOR_NEXT,
733
+ + MODEL_TENSOR.DFLASH_SELECTOR_HIDDEN,
734
+ ],
735
+ MODEL_ARCH.MISTRAL4: [
736
+ MODEL_TENSOR.TOKEN_EMBD,
737
+ diff --git a/gguf-py/gguf/gguf_writer.py b/gguf-py/gguf/gguf_writer.py
738
+ index 16ae9f999..b353fbf06 100644
739
+ --- a/gguf-py/gguf/gguf_writer.py
740
+ +++ b/gguf-py/gguf/gguf_writer.py
741
+ @@ -984,6 +984,18 @@ class GGUFWriter:
742
+ def add_block_size(self, value: int) -> None:
743
+ self.add_uint32(Keys.LLM.BLOCK_SIZE.format(arch=self.arch), value)
744
+
745
+ + def add_conv_kernel_size(self, value: int) -> None:
746
+ + self.add_uint32(Keys.LLM.CONV_KERNEL_SIZE.format(arch=self.arch), value)
747
+ +
748
+ + def add_conv_group_size(self, value: int) -> None:
749
+ + self.add_uint32(Keys.LLM.CONV_GROUP_SIZE.format(arch=self.arch), value)
750
+ +
751
+ + def add_selector_rank(self, value: int) -> None:
752
+ + self.add_uint32(Keys.LLM.SELECTOR_RANK.format(arch=self.arch), value)
753
+ +
754
+ + def add_selector_top_k(self, value: int) -> None:
755
+ + self.add_uint32(Keys.LLM.SELECTOR_TOP_K.format(arch=self.arch), value)
756
+ +
757
+ def add_sample_from_anchor(self, value: bool) -> None:
758
+ self.add_bool(Keys.LLM.SAMPLE_FROM_ANCHOR.format(arch=self.arch), value)
759
+
760
+ diff --git a/gguf-py/gguf/tensor_mapping.py b/gguf-py/gguf/tensor_mapping.py
761
+ index a0571ccd3..6c4b3656d 100644
762
+ --- a/gguf-py/gguf/tensor_mapping.py
763
+ +++ b/gguf-py/gguf/tensor_mapping.py
764
+ @@ -1354,6 +1354,34 @@ class TensorNameMap:
765
+ "model.confidence_head.proj", # dspark
766
+ ),
767
+
768
+ + MODEL_TENSOR.DFLASH_ATTN_CONV_BASE: (
769
+ + "model.layers.{bid}.attention_conv.base_kernel",
770
+ + ),
771
+ +
772
+ + MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ: (
773
+ + "model.layers.{bid}.attention_conv.kernel_projection",
774
+ + ),
775
+ +
776
+ + MODEL_TENSOR.DFLASH_FFN_CONV_BASE: (
777
+ + "model.layers.{bid}.mlp_conv.base_kernel",
778
+ + ),
779
+ +
780
+ + MODEL_TENSOR.DFLASH_FFN_CONV_PROJ: (
781
+ + "model.layers.{bid}.mlp_conv.kernel_projection",
782
+ + ),
783
+ +
784
+ + MODEL_TENSOR.DFLASH_SELECTOR_PREV: (
785
+ + "model.candidate_selector.predecessor_codebook",
786
+ + ),
787
+ +
788
+ + MODEL_TENSOR.DFLASH_SELECTOR_NEXT: (
789
+ + "model.candidate_selector.successor_codebook",
790
+ + ),
791
+ +
792
+ + MODEL_TENSOR.DFLASH_SELECTOR_HIDDEN: (
793
+ + "model.candidate_selector.hidden_projection",
794
+ + ),
795
+ +
796
+ MODEL_TENSOR.CLS: (
797
+ "classifier", # jina
798
+ "classifier.dense", # roberta
799
+ diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp
800
+ index 955c2d796..e470d0b85 100644
801
+ --- a/src/llama-arch.cpp
802
+ +++ b/src/llama-arch.cpp
803
+ @@ -337,6 +337,11 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
804
+
805
+ { LLM_KV_TARGET_LAYERS, "%s.target_layers" },
806
+ { LLM_KV_TARGET_HIDDEN_SIZE, "%s.target_hidden_size" },
807
+ + { LLM_KV_DFLASH_BLOCK_SIZE, "%s.block_size" },
808
+ + { LLM_KV_DFLASH_CONV_KERNEL_SIZE, "%s.conv_kernel_size" },
809
+ + { LLM_KV_DFLASH_CONV_GROUP_SIZE, "%s.conv_group_size" },
810
+ + { LLM_KV_DFLASH_SELECTOR_RANK, "%s.selector_rank" },
811
+ + { LLM_KV_DFLASH_SELECTOR_TOP_K, "%s.selector_top_k" },
812
+ { LLM_KV_NORM_BEFORE_RESIDUAL, "%s.norm_before_residual" },
813
+ { LLM_KV_NORM_BEFORE_FC, "%s.norm_before_fc" },
814
+
815
+ @@ -647,6 +652,13 @@ static const std::map<llm_tensor, const char *> LLM_TENSOR_NAMES = {
816
+ { LLM_TENSOR_DSPARK_MARKOV_W1, "markov_w1" },
817
+ { LLM_TENSOR_DSPARK_MARKOV_W2, "markov_w2" },
818
+ { LLM_TENSOR_DSPARK_CONF_PROJ, "conf_proj" },
819
+ + { LLM_TENSOR_DFLASH_ATTN_CONV_BASE, "blk.%d.attn_conv_base" },
820
+ + { LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, "blk.%d.attn_conv_proj" },
821
+ + { LLM_TENSOR_DFLASH_FFN_CONV_BASE, "blk.%d.ffn_conv_base" },
822
+ + { LLM_TENSOR_DFLASH_FFN_CONV_PROJ, "blk.%d.ffn_conv_proj" },
823
+ + { LLM_TENSOR_DFLASH_SELECTOR_PREV, "selector_predecessor" },
824
+ + { LLM_TENSOR_DFLASH_SELECTOR_NEXT, "selector_successor" },
825
+ + { LLM_TENSOR_DFLASH_SELECTOR_HIDDEN, "selector_hidden" },
826
+ };
827
+
828
+ // declare information about the model weight tensors:
829
+ @@ -912,6 +924,13 @@ static const std::map<llm_tensor, llm_tensor_info> LLM_TENSOR_INFOS = {
830
+ {LLM_TENSOR_DSPARK_MARKOV_W1, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
831
+ {LLM_TENSOR_DSPARK_MARKOV_W2, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
832
+ {LLM_TENSOR_DSPARK_CONF_PROJ, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
833
+ + {LLM_TENSOR_DFLASH_ATTN_CONV_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
834
+ + {LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
835
+ + {LLM_TENSOR_DFLASH_FFN_CONV_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
836
+ + {LLM_TENSOR_DFLASH_FFN_CONV_PROJ, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
837
+ + {LLM_TENSOR_DFLASH_SELECTOR_PREV, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
838
+ + {LLM_TENSOR_DFLASH_SELECTOR_NEXT, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
839
+ + {LLM_TENSOR_DFLASH_SELECTOR_HIDDEN, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
840
+ };
841
+
842
+ LLM_KV::LLM_KV(llm_arch arch, const char * suffix) : arch(arch), suffix(suffix) {}
843
+ diff --git a/src/llama-arch.h b/src/llama-arch.h
844
+ index 48fe051a9..c0f218879 100644
845
+ --- a/src/llama-arch.h
846
+ +++ b/src/llama-arch.h
847
+ @@ -383,6 +383,11 @@ enum llm_kv {
848
+
849
+ LLM_KV_TARGET_LAYERS,
850
+ LLM_KV_TARGET_HIDDEN_SIZE,
851
+ + LLM_KV_DFLASH_BLOCK_SIZE,
852
+ + LLM_KV_DFLASH_CONV_KERNEL_SIZE,
853
+ + LLM_KV_DFLASH_CONV_GROUP_SIZE,
854
+ + LLM_KV_DFLASH_SELECTOR_RANK,
855
+ + LLM_KV_DFLASH_SELECTOR_TOP_K,
856
+ LLM_KV_NORM_BEFORE_RESIDUAL,
857
+ LLM_KV_NORM_BEFORE_FC,
858
+
859
+ @@ -655,6 +660,13 @@ enum llm_tensor {
860
+ LLM_TENSOR_DSPARK_MARKOV_W1,
861
+ LLM_TENSOR_DSPARK_MARKOV_W2,
862
+ LLM_TENSOR_DSPARK_CONF_PROJ,
863
+ + LLM_TENSOR_DFLASH_ATTN_CONV_BASE,
864
+ + LLM_TENSOR_DFLASH_ATTN_CONV_PROJ,
865
+ + LLM_TENSOR_DFLASH_FFN_CONV_BASE,
866
+ + LLM_TENSOR_DFLASH_FFN_CONV_PROJ,
867
+ + LLM_TENSOR_DFLASH_SELECTOR_PREV,
868
+ + LLM_TENSOR_DFLASH_SELECTOR_NEXT,
869
+ + LLM_TENSOR_DFLASH_SELECTOR_HIDDEN,
870
+ };
871
+
872
+
873
+ diff --git a/src/llama-context.cpp b/src/llama-context.cpp
874
+ index 52f8d5367..51148b2ad 100644
875
+ --- a/src/llama-context.cpp
876
+ +++ b/src/llama-context.cpp
877
+ @@ -2314,6 +2314,12 @@ uint32_t llama_context::graph_max_nodes(uint32_t n_tokens) const {
878
+ }
879
+ }
880
+
881
+ + if (model.arch == LLM_ARCH_DFLASH && model.hparams.dflash_selector_rank > 0) {
882
+ + const uint32_t selector_tokens = std::min<uint32_t>(
883
+ + n_tokens, model.hparams.dflash_block_size * cparams.n_seq_max);
884
+ + res += 32*selector_tokens;
885
+ + }
886
+ +
887
+ uint32_t n_sampling_nodes = 0;
888
+ uint32_t n_sampling_nodes_max = 0;
889
+ for (const auto & [seq_id, sampler] : sampling.samplers) {
890
+ diff --git a/src/llama-graph.h b/src/llama-graph.h
891
+ index 94324c745..605733f32 100644
892
+ --- a/src/llama-graph.h
893
+ +++ b/src/llama-graph.h
894
+ @@ -1326,6 +1326,8 @@ struct llm_graph_context {
895
+
896
+ void build_sampling() const;
897
+
898
+ + virtual void build_post_sampling() const {}
899
+ +
900
+ //
901
+ // dense (out)
902
+ //
903
+ diff --git a/src/llama-hparams.h b/src/llama-hparams.h
904
+ index f6af36436..368ad7c70 100644
905
+ --- a/src/llama-hparams.h
906
+ +++ b/src/llama-hparams.h
907
+ @@ -218,6 +218,12 @@ struct llama_hparams {
908
+ // output embedding dimension (0 = use n_embd)
909
+ uint32_t n_embd_out_impl = 0;
910
+
911
+ + uint32_t dflash_block_size = 0;
912
+ + uint32_t dflash_conv_kernel_size = 0;
913
+ + uint32_t dflash_conv_group_size = 0;
914
+ + uint32_t dflash_selector_rank = 0;
915
+ + uint32_t dflash_selector_top_k = 0;
916
+ +
917
+ // llama4 smallthinker
918
+ uint32_t n_moe_layer_step = 0;
919
+ uint32_t n_no_rope_layer_step = 4;
920
+ diff --git a/src/llama-model.cpp b/src/llama-model.cpp
921
+ index 3759c8625..5dbd22f63 100644
922
+ --- a/src/llama-model.cpp
923
+ +++ b/src/llama-model.cpp
924
+ @@ -2462,6 +2462,7 @@ ggml_cgraph * llama_model::build_graph(const llm_graph_params & params) const {
925
+
926
+ // add backend sampling layers (if any)
927
+ llm->build_sampling();
928
+ + llm->build_post_sampling();
929
+
930
+ // if the gguf model was converted with --sentence-transformers-dense-modules
931
+ // there will be two additional dense projection layers
932
+ diff --git a/src/llama-model.h b/src/llama-model.h
933
+ index 4412ef08e..e2cd04946 100644
934
+ --- a/src/llama-model.h
935
+ +++ b/src/llama-model.h
936
+ @@ -362,6 +362,11 @@ struct llama_layer {
937
+ struct ggml_tensor * ffn_exp_probs_b = nullptr;
938
+ struct ggml_tensor * ffn_gate_tid2eid = nullptr;
939
+
940
+ + struct ggml_tensor * dflash_attn_conv_base = nullptr;
941
+ + struct ggml_tensor * dflash_attn_conv_proj = nullptr;
942
+ + struct ggml_tensor * dflash_ffn_conv_base = nullptr;
943
+ + struct ggml_tensor * dflash_ffn_conv_proj = nullptr;
944
+ +
945
+ // mamba proj
946
+ struct ggml_tensor * ssm_in = nullptr;
947
+ struct ggml_tensor * ssm_x = nullptr;
948
+ @@ -648,6 +653,10 @@ struct llama_model {
949
+ struct ggml_tensor * dspark_conf_proj = nullptr;
950
+ struct ggml_tensor * dspark_conf_proj_b = nullptr;
951
+
952
+ + struct ggml_tensor * dflash_selector_prev = nullptr;
953
+ + struct ggml_tensor * dflash_selector_next = nullptr;
954
+ + struct ggml_tensor * dflash_selector_hidden = nullptr;
955
+ +
956
+ // unified vector to store target-model extracted layer ids in eagle3, dflash, etc.
957
+ std::vector<int32_t> target_layer_ids;
958
+
959
+ diff --git a/src/models/dflash.cpp b/src/models/dflash.cpp
960
+ index 5b70a5179..776d34dd2 100644
961
+ --- a/src/models/dflash.cpp
962
+ +++ b/src/models/dflash.cpp
963
+ @@ -7,6 +7,16 @@
964
+ void llama_model_dflash::load_arch_hparams(llama_model_loader & ml) {
965
+
966
+ ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
967
+ + ml.get_key(LLM_KV_LOGIT_SCALE, hparams.f_logit_scale, false);
968
+ + hparams.f_final_logit_softcapping = 0.0f;
969
+ + ml.get_key(LLM_KV_FINAL_LOGIT_SOFTCAPPING, hparams.f_final_logit_softcapping, false);
970
+ + ml.get_key(LLM_KV_EMBEDDING_SCALE, hparams.f_embedding_scale, false);
971
+ +
972
+ + ml.get_key(LLM_KV_DFLASH_BLOCK_SIZE, hparams.dflash_block_size, false);
973
+ + ml.get_key(LLM_KV_DFLASH_CONV_KERNEL_SIZE, hparams.dflash_conv_kernel_size, false);
974
+ + ml.get_key(LLM_KV_DFLASH_CONV_GROUP_SIZE, hparams.dflash_conv_group_size, false);
975
+ + ml.get_key(LLM_KV_DFLASH_SELECTOR_RANK, hparams.dflash_selector_rank, false);
976
+ + ml.get_key(LLM_KV_DFLASH_SELECTOR_TOP_K, hparams.dflash_selector_top_k, false);
977
+
978
+ if (!ml.get_arr(LLM_KV_TARGET_LAYERS, target_layer_ids, false)) {
979
+ throw std::runtime_error("DFlash model requires 'target_layers' in GGUF metadata");
980
+ @@ -112,6 +122,29 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) {
981
+ LLAMA_LOG_INFO("%s: DFlash with DSpark markov head (rank = %lld)\n", __func__, (long long) dspark_markov_rank);
982
+ }
983
+
984
+ + const struct ggml_tensor * selector_meta = ml->get_tensor_meta("selector_hidden.weight");
985
+ + if (selector_meta) {
986
+ + const int64_t rank = hparams.dflash_selector_rank;
987
+ + if (rank <= 0 || hparams.dflash_block_size <= 0 || hparams.dflash_selector_top_k <= 0 ||
988
+ + hparams.dflash_conv_kernel_size <= 0 || hparams.dflash_conv_group_size <= 0) {
989
+ + throw std::runtime_error("DFlash2 model is missing conv/selector metadata");
990
+ + }
991
+ + if (n_embd % hparams.dflash_conv_group_size != 0) {
992
+ + throw std::runtime_error("DFlash2 hidden size must be divisible by conv_group_size");
993
+ + }
994
+ + if (n_embd < hparams.dflash_selector_top_k * (hparams.dflash_selector_top_k + 1)) {
995
+ + throw std::runtime_error("DFlash2 hidden size is too small for the selector lattice");
996
+ + }
997
+ +
998
+ + dflash_selector_prev = create_tensor(tn(LLM_TENSOR_DFLASH_SELECTOR_PREV, "weight"), { rank, n_vocab }, 0);
999
+ + dflash_selector_next = create_tensor(tn(LLM_TENSOR_DFLASH_SELECTOR_NEXT, "weight"), { rank, n_vocab }, 0);
1000
+ + dflash_selector_hidden = create_tensor(tn(LLM_TENSOR_DFLASH_SELECTOR_HIDDEN, "weight"), { n_embd, rank }, 0);
1001
+ +
1002
+ + LLAMA_LOG_INFO("%s: DFlash2 conv kernel = %u, group = %u, selector rank = %u, top-k = %u\n", __func__,
1003
+ + hparams.dflash_conv_kernel_size, hparams.dflash_conv_group_size,
1004
+ + hparams.dflash_selector_rank, hparams.dflash_selector_top_k);
1005
+ + }
1006
+ +
1007
+ fc = create_tensor(tn(LLM_TENSOR_FC, "weight"), { n_embd_inp, n_embd }, 0);
1008
+ fc_s = create_tensor(tn(LLM_TENSOR_FC, "scale"), { 1 }, TENSOR_NOT_REQUIRED);
1009
+ output_norm_enc = create_tensor(tn(LLM_TENSOR_ENC_OUTPUT_NORM, "weight"), { n_embd }, 0); // encoder hidden_norm (after fc)
1010
+ @@ -187,6 +220,16 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) {
1011
+ layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), { n_embd, n_ff }, 0);
1012
+ layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd }, 0);
1013
+ layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), { n_embd, n_ff }, 0);
1014
+ +
1015
+ + if (selector_meta) {
1016
+ + const int64_t kernel = hparams.dflash_conv_kernel_size;
1017
+ + const int64_t groups = n_embd / hparams.dflash_conv_group_size;
1018
+ + const int64_t projected = 2 * kernel * groups;
1019
+ + layer.dflash_attn_conv_base = create_tensor(tn(LLM_TENSOR_DFLASH_ATTN_CONV_BASE, i), { n_embd, kernel, 2 }, 0);
1020
+ + layer.dflash_attn_conv_proj = create_tensor(tn(LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, "weight", i), { n_embd, projected }, 0);
1021
+ + layer.dflash_ffn_conv_base = create_tensor(tn(LLM_TENSOR_DFLASH_FFN_CONV_BASE, i), { n_embd, kernel, 2 }, 0);
1022
+ + layer.dflash_ffn_conv_proj = create_tensor(tn(LLM_TENSOR_DFLASH_FFN_CONV_PROJ, "weight", i), { n_embd, projected }, 0);
1023
+ + }
1024
+ }
1025
+ }
1026
+
1027
+ @@ -222,7 +265,7 @@ ggml_tensor * llama_model_dflash::graph<true>::build_inp_embd_enc() const {
1028
+
1029
+ // DFlash Encoder: processes target model features through feature fusion layer
1030
+ template <>
1031
+ -llama_model_dflash::graph<true>::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) {
1032
+ +llama_model_dflash::graph<true>::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params), model(model) {
1033
+ ggml_tensor * cur = build_inp_embd_enc();
1034
+
1035
+ cur = build_lora_mm(model.fc, cur, model.fc_s);
1036
+ @@ -345,11 +388,69 @@ static void build_dspark_markov_head(llm_graph_context & g, const llama_model &
1037
+ ggml_build_forward_expand(g.gf, out);
1038
+ }
1039
+
1040
+ +static ggml_tensor * build_dflash2_conv(
1041
+ + llm_graph_context & g,
1042
+ + ggml_tensor * hidden,
1043
+ + ggml_tensor * dynamic,
1044
+ + ggml_tensor * base,
1045
+ + int side) {
1046
+ + const auto & hparams = g.hparams;
1047
+ + const int64_t hidden_size = hidden->ne[0];
1048
+ + const int64_t n_tokens = hidden->ne[1];
1049
+ + const int64_t n_blocks = g.ubatch.n_seqs_unq;
1050
+ + const int64_t kernel_size = hparams.dflash_conv_kernel_size;
1051
+ + const int64_t group_size = hparams.dflash_conv_group_size;
1052
+ + const int64_t n_groups = hidden_size / group_size;
1053
+ +
1054
+ + GGML_ASSERT(n_blocks > 0 && n_tokens % n_blocks == 0);
1055
+ + GGML_ASSERT(dynamic && base && side >= 0 && side < 2);
1056
+ +
1057
+ + const int64_t block_size = n_tokens / n_blocks;
1058
+ + ggml_context * ctx0 = g.ctx0;
1059
+ + hidden = ggml_cont_2d(ctx0, hidden, hidden_size, n_tokens);
1060
+ + dynamic = ggml_cont_2d(ctx0, dynamic, dynamic->ne[0], n_tokens);
1061
+ + ggml_tensor * blocks = ggml_reshape_3d(ctx0, hidden, hidden_size, block_size, n_blocks);
1062
+ + ggml_tensor * grouped = ggml_reshape_3d(ctx0, hidden, group_size, n_groups, n_tokens);
1063
+ + ggml_tensor * coeffs = ggml_reshape_4d(ctx0, dynamic, n_groups, kernel_size, 2, n_tokens);
1064
+ + ggml_tensor * coeffs_side = ggml_view_3d(ctx0, coeffs, n_groups, kernel_size, n_tokens,
1065
+ + coeffs->nb[1], coeffs->nb[3], side * coeffs->nb[2]);
1066
+ +
1067
+ + ggml_tensor * result = nullptr;
1068
+ + for (int64_t tap = 0; tap < kernel_size; ++tap) {
1069
+ + ggml_tensor * values = blocks;
1070
+ + if (tap > 0) {
1071
+ + ggml_tensor * zeros = ggml_fill(ctx0,
1072
+ + ggml_new_tensor_3d(ctx0, hidden->type, hidden_size, std::min(tap, block_size), n_blocks), 0.0f);
1073
+ + if (tap < block_size) {
1074
+ + ggml_tensor * previous = ggml_view_3d(ctx0, blocks, hidden_size, block_size - tap, n_blocks,
1075
+ + blocks->nb[1], blocks->nb[2], 0);
1076
+ + values = ggml_concat(ctx0, zeros, previous, 1);
1077
+ + } else {
1078
+ + values = zeros;
1079
+ + }
1080
+ + }
1081
+ + values = ggml_reshape_2d(ctx0, values, hidden_size, n_tokens);
1082
+ +
1083
+ + ggml_tensor * coeff = ggml_view_2d(ctx0, coeffs_side, n_groups, n_tokens,
1084
+ + coeffs_side->nb[2], tap * coeffs_side->nb[1]);
1085
+ + coeff = ggml_cont(ctx0, coeff);
1086
+ + coeff = ggml_reshape_3d(ctx0, coeff, 1, n_groups, n_tokens);
1087
+ + coeff = ggml_reshape_2d(ctx0, ggml_repeat(ctx0, coeff, grouped), hidden_size, n_tokens);
1088
+ +
1089
+ + ggml_tensor * base_tap = ggml_view_1d(ctx0, base, hidden_size,
1090
+ + tap * base->nb[1] + side * base->nb[2]);
1091
+ + ggml_tensor * weight = ggml_add(ctx0, coeff, ggml_repeat(ctx0, base_tap, hidden));
1092
+ + ggml_tensor * term = ggml_mul(ctx0, weight, values);
1093
+ + result = result ? ggml_add(ctx0, result, term) : term;
1094
+ + }
1095
+ + return result;
1096
+ +}
1097
+ +
1098
+ // DFlash decoder, dual-mode by batch type:
1099
+ // * embd batch -> fused target features: project + inject K/V into the cache.
1100
+ // * token batch -> noise-block diffusion: attend over [committed, MASK...] to generate draft tokens
1101
+ template <>
1102
+ -llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) {
1103
+ +llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params), model(model) {
1104
+ const int64_t n_embd_head = hparams.n_embd_head_v();
1105
+
1106
+ GGML_ASSERT(n_embd_head == hparams.n_embd_head_k());
1107
+ @@ -449,10 +550,14 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
1108
+
1109
+ inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens);
1110
+ ggml_set_input(inp->tokens);
1111
+ + res->t_inp_tokens = inp->tokens;
1112
+
1113
+ ggml_tensor * inp_tokens = inp->tokens;
1114
+
1115
+ ggml_tensor * inpL = ggml_get_rows(ctx0, tok_embd, inp->tokens);
1116
+ + if (hparams.f_embedding_scale != 0.0f) {
1117
+ + inpL = ggml_scale(ctx0, inpL, hparams.f_embedding_scale);
1118
+ + }
1119
+ cb(inpL, "inp_noise_embd", -1);
1120
+
1121
+ res->add_input(std::move(inp));
1122
+ @@ -463,6 +568,13 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
1123
+ ggml_tensor * noise_norm = build_norm(inpL, layer.attn_norm, NULL, LLM_NORM_RMS, il);
1124
+ cb(noise_norm, "noise_norm", il);
1125
+
1126
+ + ggml_tensor * attn_dynamic = nullptr;
1127
+ + if (layer.dflash_attn_conv_proj) {
1128
+ + attn_dynamic = build_lora_mm(layer.dflash_attn_conv_proj, noise_norm);
1129
+ + noise_norm = build_dflash2_conv(*this, noise_norm, attn_dynamic, layer.dflash_attn_conv_base, 0);
1130
+ + cb(noise_norm, "attn_conv_in", il);
1131
+ + }
1132
+ +
1133
+ ggml_tensor * Qcur = build_lora_mm(layer.wq, noise_norm);
1134
+ ggml_tensor * Kcur = build_lora_mm(layer.wk, noise_norm);
1135
+ ggml_tensor * Vcur = build_lora_mm(layer.wv, noise_norm);
1136
+ @@ -493,12 +605,24 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
1137
+ ? build_attn(inp_attn_iswa, layer.wo, NULL, NULL, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il)
1138
+ : build_attn(inp_attn, layer.wo, NULL, NULL, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);
1139
+
1140
+ + if (attn_dynamic) {
1141
+ + cur = build_dflash2_conv(*this, cur, attn_dynamic, layer.dflash_attn_conv_base, 1);
1142
+ + cb(cur, "attn_conv_out", il);
1143
+ + }
1144
+ +
1145
+ ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpL);
1146
+ cb(ffn_inp, "ffn_inp", il);
1147
+
1148
+ cur = build_norm(ffn_inp, layer.ffn_norm, NULL, LLM_NORM_RMS, il);
1149
+ cb(cur, "ffn_norm", il);
1150
+
1151
+ + ggml_tensor * ffn_dynamic = nullptr;
1152
+ + if (layer.dflash_ffn_conv_proj) {
1153
+ + ffn_dynamic = build_lora_mm(layer.dflash_ffn_conv_proj, cur);
1154
+ + cur = build_dflash2_conv(*this, cur, ffn_dynamic, layer.dflash_ffn_conv_base, 0);
1155
+ + cb(cur, "ffn_conv_in", il);
1156
+ + }
1157
+ +
1158
+ cur = build_ffn(cur,
1159
+ layer.ffn_up, NULL, layer.ffn_up_s,
1160
+ layer.ffn_gate, NULL, layer.ffn_gate_s,
1161
+ @@ -507,6 +631,11 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
1162
+ LLM_FFN_SILU, LLM_FFN_PAR, il);
1163
+ cb(cur, "ffn_out", il);
1164
+
1165
+ + if (ffn_dynamic) {
1166
+ + cur = build_dflash2_conv(*this, cur, ffn_dynamic, layer.dflash_ffn_conv_base, 1);
1167
+ + cb(cur, "ffn_conv_out", il);
1168
+ + }
1169
+ +
1170
+ cur = ggml_add(ctx0, cur, ffn_inp);
1171
+ cb(cur, "l_out", il);
1172
+
1173
+ @@ -531,6 +660,15 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
1174
+
1175
+ cur = build_lora_mm(output, cur, output_s);
1176
+
1177
+ + if (hparams.f_logit_scale != 0.0f) {
1178
+ + cur = ggml_scale(ctx0, cur, hparams.f_logit_scale);
1179
+ + }
1180
+ + if (hparams.f_final_logit_softcapping > 0.0f) {
1181
+ + cur = ggml_scale(ctx0, cur, 1.0f / hparams.f_final_logit_softcapping);
1182
+ + cur = ggml_tanh(ctx0, cur);
1183
+ + cur = ggml_scale(ctx0, cur, hparams.f_final_logit_softcapping);
1184
+ + }
1185
+ +
1186
+ // reduced-draft-vocab exports: scatter the draft logits to the target vocabulary via d2t
1187
+ if (model.d2t) {
1188
+ const int64_t n_draft_vocab = cur->ne[0];
1189
+ @@ -557,6 +695,98 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
1190
+ }
1191
+ }
1192
+
1193
+ +template <bool is_enc>
1194
+ +void llama_model_dflash::graph<is_enc>::build_post_sampling() const {
1195
+ + if constexpr (is_enc) {
1196
+ + return;
1197
+ + }
1198
+ +
1199
+ + if (!model.dflash_selector_hidden || !res->t_logits) {
1200
+ + return;
1201
+ + }
1202
+ +
1203
+ + const int64_t top_k = hparams.dflash_selector_top_k;
1204
+ + const int64_t rank = hparams.dflash_selector_rank;
1205
+ + const int64_t n_blocks = ubatch.n_seqs_unq;
1206
+ + GGML_ASSERT(n_blocks > 0 && n_tokens % n_blocks == 0);
1207
+ + GGML_ASSERT(res->t_logits->ne[1] == n_tokens);
1208
+ + ggml_tensor * tokens = res->get_inp_tokens();
1209
+ + if (!tokens) {
1210
+ + return;
1211
+ + }
1212
+ +
1213
+ + const int64_t tokens_per_block = n_tokens / n_blocks;
1214
+ + const int64_t block_size = std::min<int64_t>(tokens_per_block, hparams.dflash_block_size);
1215
+ + ggml_tensor * candidates = ggml_top_k(ctx0, res->t_logits, top_k);
1216
+ + ggml_tensor * logits_rows = ggml_reshape_3d(ctx0, res->t_logits, 1, res->t_logits->ne[0], n_tokens);
1217
+ + ggml_tensor * unary = ggml_reshape_2d(ctx0,
1218
+ + ggml_get_rows(ctx0, logits_rows, candidates), top_k, n_tokens);
1219
+ +
1220
+ + std::vector<ggml_tensor *> candidate_ids(block_size);
1221
+ + std::vector<ggml_tensor *> unary_logits(block_size);
1222
+ + for (int64_t pos = 1; pos < block_size; ++pos) {
1223
+ + candidate_ids[pos] = ggml_cont_2d(ctx0,
1224
+ + ggml_view_2d(ctx0, candidates, top_k, n_blocks,
1225
+ + tokens_per_block * candidates->nb[1], pos * candidates->nb[1]),
1226
+ + top_k, n_blocks);
1227
+ + unary_logits[pos] = ggml_cont_2d(ctx0,
1228
+ + ggml_view_2d(ctx0, unary, top_k, n_blocks,
1229
+ + tokens_per_block * unary->nb[1], pos * unary->nb[1]),
1230
+ + top_k, n_blocks);
1231
+ + }
1232
+ +
1233
+ + ggml_tensor * hidden = build_lora_mm(model.dflash_selector_hidden, res->t_embd);
1234
+ +
1235
+ + ggml_tensor * anchor_ids = ggml_view_2d(ctx0, tokens, 1, n_blocks,
1236
+ + tokens_per_block * tokens->nb[0], 0);
1237
+ + anchor_ids = ggml_cont_1d(ctx0, anchor_ids, n_blocks);
1238
+ +
1239
+ + ggml_tensor * packed = ggml_fill(ctx0,
1240
+ + ggml_new_tensor_3d(ctx0, GGML_TYPE_F32, n_embd, 1, n_blocks), 0.0f);
1241
+ +
1242
+ + for (int64_t pos = 1; pos < block_size; ++pos) {
1243
+ + ggml_tensor * ids = candidate_ids[pos];
1244
+ + ggml_tensor * unary = unary_logits[pos];
1245
+ + ggml_tensor * successor = ggml_get_rows(ctx0, model.dflash_selector_next,
1246
+ + ggml_reshape_1d(ctx0, ids, top_k * n_blocks));
1247
+ + successor = ggml_reshape_3d(ctx0, successor, rank, top_k, n_blocks);
1248
+ +
1249
+ + ggml_tensor * hidden_pos = ggml_cont(ctx0, ggml_view_2d(ctx0, hidden, rank, n_blocks,
1250
+ + tokens_per_block * hidden->nb[1], pos * hidden->nb[1]));
1251
+ + hidden_pos = ggml_reshape_3d(ctx0, hidden_pos, rank, 1, n_blocks);
1252
+ +
1253
+ + ggml_tensor * predecessor;
1254
+ + if (pos == 1) {
1255
+ + predecessor = ggml_get_rows(ctx0, model.dflash_selector_prev, anchor_ids);
1256
+ + predecessor = ggml_reshape_3d(ctx0, predecessor, rank, 1, n_blocks);
1257
+ + } else {
1258
+ + predecessor = ggml_get_rows(ctx0, model.dflash_selector_prev,
1259
+ + ggml_reshape_1d(ctx0, candidate_ids[pos - 1], top_k * n_blocks));
1260
+ + predecessor = ggml_reshape_3d(ctx0, predecessor, rank, top_k, n_blocks);
1261
+ + }
1262
+ +
1263
+ + ggml_tensor * conditioned = ggml_mul(ctx0, predecessor, ggml_repeat(ctx0, hidden_pos, predecessor));
1264
+ + ggml_tensor * scores = ggml_mul_mat(ctx0, successor, conditioned);
1265
+ + if (pos == 1) {
1266
+ + scores = ggml_repeat_4d(ctx0, scores, top_k, top_k, n_blocks, 1);
1267
+ + }
1268
+ + ggml_tensor * unary_3d = ggml_reshape_3d(ctx0, unary, top_k, 1, n_blocks);
1269
+ + scores = ggml_add(ctx0, scores, ggml_repeat(ctx0, unary_3d, scores));
1270
+ +
1271
+ + ggml_tensor * row = ggml_concat(ctx0,
1272
+ + ggml_cast(ctx0, ids, GGML_TYPE_F32),
1273
+ + ggml_reshape_2d(ctx0, scores, top_k * top_k, n_blocks), 0);
1274
+ + row = ggml_pad(ctx0, row, n_embd - row->ne[0], 0, 0, 0);
1275
+ + row = ggml_reshape_3d(ctx0, row, n_embd, 1, n_blocks);
1276
+ + packed = ggml_concat(ctx0, packed, row, 1);
1277
+ + }
1278
+ +
1279
+ + packed = ggml_reshape_2d(ctx0, packed, n_embd, block_size * n_blocks);
1280
+ + cb(packed, "dflash2_lattice", -1);
1281
+ + res->t_h_nextn = packed;
1282
+ + ggml_build_forward_expand(gf, packed);
1283
+ +}
1284
+ +
1285
+ // DSV4 DSpark decoder, dual-mode by batch type (see the DFlash decoder above):
1286
+ // * embd batch -> project main_x through each stage's wkv and inject K into the ring cache
1287
+ // * token batch -> noise block through 3 full DSV4 stages (hc + MLA + MoE), markov + confidence heads
1288
+ diff --git a/src/models/models.h b/src/models/models.h
1289
+ index 1dd30dfd1..f4a72fcba 100644
1290
+ --- a/src/models/models.h
1291
+ +++ b/src/models/models.h
1292
+ @@ -1342,9 +1342,12 @@ struct llama_model_dflash : public llama_model_base {
1293
+
1294
+ template <bool is_enc>
1295
+ struct graph : public llm_graph_context {
1296
+ + const llama_model & model;
1297
+ +
1298
+ graph(const llama_model & model, const llm_graph_params & params);
1299
+
1300
+ ggml_tensor * build_inp_embd_enc() const;
1301
+ + void build_post_sampling() const override;
1302
+ };
1303
+
1304
+ struct graph_dsv4 : public llama_model_deepseek4::graph {
1305
+ diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp
1306
+ index 21ff78394..84665ae86 100644
1307
+ --- a/tools/server/server-context.cpp
1308
+ +++ b/tools/server/server-context.cpp
1309
+ @@ -209,6 +209,7 @@ struct server_slot {
1310
+ common_speculative * spec;
1311
+
1312
+ llama_tokens spec_draft;
1313
+ + std::vector<common_speculative_token_dist> spec_dists;
1314
+ llama_tokens spec_prompt;
1315
+ std::vector<int32_t> spec_i_batch;
1316
+ common_prompt_checkpoint spec_ckpt;
1317
+ @@ -337,6 +338,7 @@ struct server_slot {
1318
+
1319
+ if (can_speculate()) {
1320
+ spec_draft.clear();
1321
+ + spec_dists.clear();
1322
+ spec_i_batch.clear();
1323
+ spec_ckpt.clear();
1324
+ }
1325
+ @@ -2961,6 +2963,9 @@ private:
1326
+ /* .id_last = */ slot.sampled,
1327
+ /* .prompt = */ &slot.spec_prompt,
1328
+ /* .result = */ &slot.spec_draft,
1329
+ + /* .dists = */ &slot.spec_dists,
1330
+ + /* .temperature = */ slot.task->params.sampling.temp,
1331
+ + /* .seed = */ common_sampler_get_seed(slot.smpl.get()),
1332
+ };
1333
+
1334
+ drafting.push_back(&slot);
1335
+ @@ -3840,7 +3845,13 @@ private:
1336
+ common_sampler_ptr smpl_save(common_sampler_clone(slot.smpl.get()));
1337
+
1338
+ GGML_ASSERT(slot.spec_i_batch.size() == n_draft + 1);
1339
+ - auto accepted = common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft);
1340
+ + const bool can_rollback =
1341
+ + ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_PART ||
1342
+ + (ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_RS && n_draft <= llama_n_rs_seq(ctx_tgt));
1343
+ + auto accepted = can_rollback && slot.task->params.sampling.temp > 0.0f &&
1344
+ + slot.spec_dists.size() == slot.spec_draft.size()
1345
+ + ? common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft, slot.spec_dists)
1346
+ + : common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft);
1347
+ slot.spec_i_batch.clear();
1348
+
1349
+ GGML_ASSERT(accepted.size() >= 1);
1350
+ @@ -3861,6 +3872,7 @@ private:
1351
+ // partial acceptance is not supported by the context -> truncate the draft and restore the state
1352
+ slot.spec_is_replay = true;
1353
+ slot.spec_draft = std::move(accepted);
1354
+ + slot.spec_dists.clear();
1355
+
1356
+ const auto & ckpt = slot.spec_ckpt;
1357
+
1358
+ @@ -3888,6 +3900,7 @@ private:
1359
+ common_speculative_accept(spec.get(), slot.id, accepted.size() - 1);
1360
+
1361
+ slot.spec_draft = std::move(accepted);
1362
+ + slot.spec_dists.clear();
1363
+ }
1364
+
1365
+ const auto ids = std::move(slot.spec_draft);