char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Frank Daimodel : support Kimi-K3 recurrent-state rollback (#28466)9dcf84e5a

char/aliceai-t5-moe
16.6 KiB467 linesraw
1#include "arg.h"
2#include "common.h"
3#include "ggml-backend.h"
4#include "llama.h"
5
6#include "../src/llama-io.h"
7#include "../src/llama-memory.h"
8
9#include <algorithm>
10#include <clocale>
11#include <cmath>
12#include <cstdio>
13#include <limits>
14#include <set>
15#include <vector>
16
17static bool decode_tokens(llama_context * ctx, const std::vector<llama_token> & tokens, uint32_t count) {
18    llama_batch batch = llama_batch_init(count, 0, 1);
19    for (uint32_t pos = 0; pos < count; ++pos) {
20        common_batch_add(batch, tokens[pos], pos, { 0 }, pos + 1 == count);
21    }
22    const bool ok = llama_decode(ctx, batch) == 0;
23    llama_batch_free(batch);
24    return ok;
25}
26
27static bool decode_one(llama_context * ctx, llama_token tok, llama_pos pos) {
28    llama_batch batch = llama_batch_init(1, 0, 1);
29    common_batch_add(batch, tok, pos, { 0 }, true);
30    const bool ok = llama_decode(ctx, batch) == 0;
31    llama_batch_free(batch);
32    return ok;
33}
34
35struct cache_buffer_collector : llama_io_write_i {
36    std::set<ggml_backend_buffer_t> buffers;
37    size_t size = 0;
38
39    void write(const void *, size_t n) override {
40        size += n;
41    }
42
43    void write_tensor(ggml_tensor * tensor, size_t, size_t n) override {
44        buffers.insert(tensor->buffer);
45        size += n;
46    }
47
48    size_t n_bytes() override {
49        return size;
50    }
51};
52
53static llama_context * init_ctx(llama_model * model, llama_context_params cparams, uint8_t fill) {
54    llama_context * ctx = llama_init_from_model(model, cparams);
55    if (ctx == nullptr || fill == 0) {
56        return ctx;
57    }
58
59    // Use a full ubatch so buffer discovery preserves prefill allocation sizes.
60    const uint32_t n_tokens = llama_n_ubatch(ctx);
61    if (!decode_tokens(ctx, std::vector<llama_token>(n_tokens, 0), n_tokens)) {
62        llama_free(ctx);
63        return nullptr;
64    }
65    llama_synchronize(ctx);
66    cache_buffer_collector collector;
67    llama_get_memory(ctx)->state_write(collector);
68    llama_memory_clear(llama_get_memory(ctx), true);
69    if (collector.buffers.empty()) {
70        fprintf(stderr, "%s : no cache buffers found\n", __func__);
71        llama_free(ctx);
72        return nullptr;
73    }
74    for (auto * buffer : collector.buffers) {
75        ggml_backend_buffer_clear(buffer, fill);
76    }
77    return ctx;
78}
79
80static llama_context * make_ctx(const common_params & params, llama_model * model, uint8_t fill) {
81    auto cparams = common_context_params_to_llama(params);
82    cparams.n_seq_max = 1;
83    cparams.n_rs_seq  = 8;
84    cparams.n_batch   = std::max(cparams.n_batch,  (uint32_t) (cparams.n_rs_seq + 1));
85    cparams.n_ubatch  = std::max(cparams.n_ubatch, (uint32_t) (cparams.n_rs_seq + 1));
86    return init_ctx(model, cparams, fill);
87}
88
89static float logit_diff(float a, float b) {
90    return std::isfinite(a) && std::isfinite(b) ? std::fabs(a - b) : std::numeric_limits<float>::infinity();
91}
92
93// Roll back multiple sequences, then replay them in a single batch whose
94// per-seq token count exceeds n_ubatch: each seq's replay spans several
95// ubatches while its rollback restore is still pending. Compared against a
96// reference context that never advanced past the rollback point and decodes
97// the identical replay batch.
98static bool test_multi_seq_split_replay(const common_params & params, llama_model * model, const int n_vocab, uint8_t fill) {
99    constexpr uint32_t  n_seqs     = 2;
100    constexpr uint32_t  n_ubatch   = 16;
101    constexpr uint32_t  n_prompt   = 19;
102    constexpr uint32_t  n_rollback = 3;
103    constexpr uint32_t  n_replay   = 40; // > n_ubatch so each seq spans multiple ubatches
104    constexpr llama_pos p0         = n_prompt - n_rollback;
105
106    const auto make_ctx_multi = [&]() {
107        auto cparams = common_context_params_to_llama(params);
108        cparams.n_seq_max  = n_seqs;
109        cparams.n_rs_seq   = 8;
110        cparams.n_ctx      = 256;
111        cparams.n_batch    = 256;
112        cparams.n_ubatch   = n_ubatch;
113        cparams.kv_unified = false;
114        return init_ctx(model, cparams, fill);
115    };
116
117    llama_context * ctx_roll = make_ctx_multi();
118    llama_context * ctx_ref  = make_ctx_multi();
119    if (ctx_roll == nullptr || ctx_ref == nullptr) {
120        fprintf(stderr, "%s : failed to init multi-seq contexts\n", __func__);
121        return false;
122    }
123
124    const auto cleanup = [&]() {
125        llama_free(ctx_roll);
126        llama_free(ctx_ref);
127    };
128
129    if (llama_n_rs_seq(ctx_roll) < n_rollback) {
130        fprintf(stderr, "%s : skipping because n_rs_seq is too small\n", __func__);
131        cleanup();
132        return true;
133    }
134
135    const auto tok = [&](uint32_t seq, llama_pos pos) {
136        return (llama_token) ((7*(uint32_t) pos + 31*seq + 1) % (uint32_t) n_vocab);
137    };
138
139    bool ok = true;
140
141    // both contexts decode the identical [0, p0) prefill; only ctx_roll decodes
142    // the tail, which is then rolled back so its restore is pending at replay
143    for (uint32_t s = 0; s < n_seqs && ok; ++s) {
144        llama_batch batch = llama_batch_init(n_prompt, 0, 1);
145        for (llama_pos pos = 0; pos < (llama_pos) p0; ++pos) {
146            common_batch_add(batch, tok(s, pos), pos, { (llama_seq_id) s }, false);
147        }
148        ok = ok && llama_decode(ctx_roll, batch) == 0;
149        ok = ok && llama_decode(ctx_ref,  batch) == 0;
150
151        common_batch_clear(batch);
152        for (llama_pos pos = p0; pos < (llama_pos) n_prompt; ++pos) {
153            common_batch_add(batch, tok(s, pos), pos, { (llama_seq_id) s }, false);
154        }
155        ok = ok && llama_decode(ctx_roll, batch) == 0;
156        llama_batch_free(batch);
157
158        ok = ok && llama_memory_seq_rm(llama_get_memory(ctx_roll), (llama_seq_id) s, p0, -1);
159
160        // a second partial removal while one is pending must be refused
161        ok = ok && !llama_memory_seq_rm(llama_get_memory(ctx_roll), (llama_seq_id) s, p0 - 1, -1);
162    }
163    if (!ok) {
164        fprintf(stderr, "%s : multi-seq prefill/rollback failed\n", __func__);
165        cleanup();
166        return false;
167    }
168
169    llama_batch batch = llama_batch_init(n_seqs*n_replay, 0, 1);
170    for (uint32_t s = 0; s < n_seqs; ++s) {
171        for (uint32_t i = 0; i < n_replay; ++i) {
172            const llama_pos pos = p0 + (llama_pos) i;
173            common_batch_add(batch, tok(s, pos), pos, { (llama_seq_id) s }, true);
174        }
175    }
176    ok = llama_decode(ctx_roll, batch) == 0;
177    ok = ok && llama_decode(ctx_ref, batch) == 0;
178    llama_batch_free(batch);
179    if (!ok) {
180        fprintf(stderr, "%s : multi-seq replay decode failed\n", __func__);
181        cleanup();
182        return false;
183    }
184
185    // identical ubatch shapes from bit-exact states: a correct implementation
186    // matches bitwise, so eps only allows backend scheduling noise
187    constexpr float eps = 1e-7f;
188
189    float    diff_max  = 0.0f;
190    uint32_t seq_first = 0;
191    int32_t  pos_first = -1;
192    for (uint32_t i = 0; i < n_seqs*n_replay; ++i) {
193        const float * l_roll = llama_get_logits_ith(ctx_roll, i);
194        const float * l_ref  = llama_get_logits_ith(ctx_ref,  i);
195        if (l_roll == nullptr || l_ref == nullptr) {
196            fprintf(stderr, "%s : missing multi-seq logits at index %u\n", __func__, i);
197            cleanup();
198            return false;
199        }
200        for (int t = 0; t < n_vocab; ++t) {
201            const float diff = logit_diff(l_roll[t], l_ref[t]);
202            if (diff > eps && pos_first < 0) {
203                seq_first = i/n_replay;
204                pos_first = p0 + (int32_t) (i%n_replay);
205            }
206            diff_max = std::max(diff_max, diff);
207        }
208    }
209
210    if (diff_max > eps) {
211        fprintf(stderr, "%s : multi-seq split replay logits mismatch (max diff %g, first at seq %u pos %d)\n",
212                __func__, (double) diff_max, seq_first, pos_first);
213        cleanup();
214        return false;
215    }
216
217    fprintf(stderr, "%s : multi-seq split replay matched (max diff %g)\n", __func__, (double) diff_max);
218
219    // seq-1-only decodes must be independent of seq 0's content: diverge seq 0
220    // in ctx_ref only, then compare identical seq-1-only continuations bitwise
221    constexpr uint32_t n_tail = 4;
222
223    {
224        llama_batch batch_tail = llama_batch_init(n_tail, 0, 1);
225        for (uint32_t i = 0; i < n_tail; ++i) {
226            const llama_pos pos = p0 + (llama_pos) (n_replay + i);
227            common_batch_add(batch_tail, tok(0, pos + 7), pos, { 0 }, false);
228        }
229        ok = llama_decode(ctx_ref, batch_tail) == 0;
230        llama_batch_free(batch_tail);
231    }
232
233    float diff_tail = 0.0f;
234    for (uint32_t i = 0; i < n_tail && ok; ++i) {
235        const llama_pos pos = p0 + (llama_pos) (n_replay + i);
236        llama_batch batch_one = llama_batch_init(1, 0, 1);
237        common_batch_add(batch_one, tok(1, pos), pos, { 1 }, true);
238        ok = llama_decode(ctx_roll, batch_one) == 0;
239        ok = ok && llama_decode(ctx_ref, batch_one) == 0;
240        llama_batch_free(batch_one);
241        if (!ok) {
242            break;
243        }
244
245        const float * l_roll = llama_get_logits_ith(ctx_roll, 0);
246        const float * l_ref  = llama_get_logits_ith(ctx_ref,  0);
247        ok = l_roll != nullptr && l_ref != nullptr;
248        for (int t = 0; ok && t < n_vocab; ++t) {
249            diff_tail = std::max(diff_tail, logit_diff(l_roll[t], l_ref[t]));
250        }
251    }
252
253    if (!ok || diff_tail > eps) {
254        fprintf(stderr, "%s : seq-1-only decode leaked seq 0 state (ok=%d, max diff %g)\n",
255                __func__, ok ? 1 : 0, (double) diff_tail);
256        cleanup();
257        return false;
258    }
259
260    fprintf(stderr, "%s : seq-1-only decode independent of seq 0 (max diff %g)\n", __func__, (double) diff_tail);
261    cleanup();
262    return true;
263}
264
265static int test_rollback(const common_params & params, llama_model * model, uint8_t fill) {
266    const llama_vocab * vocab   = llama_model_get_vocab(model);
267    const int           n_vocab = llama_vocab_n_tokens(vocab);
268
269    llama_context * ctx_src = make_ctx(params, model, fill);
270    llama_context * ctx_dst = make_ctx(params, model, fill);
271    if (ctx_src == nullptr || ctx_dst == nullptr) {
272        fprintf(stderr, "%s : failed to init contexts\n", __func__);
273        return 1;
274    }
275
276    if (llama_n_rs_seq(ctx_src) == 0) {
277        fprintf(stderr, "%s : skipping because n_rs_seq is disabled\n", __func__);
278        llama_free(ctx_src);
279        llama_free(ctx_dst);
280        return 0;
281    }
282
283    std::vector<llama_token> tokens;
284    if (llama_vocab_type(vocab) == LLAMA_VOCAB_TYPE_NONE) {
285        tokens = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
286    } else {
287        tokens = common_tokenize(ctx_src, "The quick brown fox jumps over the lazy dog", true);
288    }
289    const uint32_t n_rs_seq = llama_n_rs_seq(ctx_src);
290    constexpr uint32_t n_rollback = 3;
291    if (n_rs_seq < n_rollback) {
292        fprintf(stderr, "%s : skipping because n_rs_seq is too small\n", __func__);
293        llama_free(ctx_src);
294        llama_free(ctx_dst);
295        return 0;
296    }
297    if (tokens.empty()) {
298        fprintf(stderr, "%s : not enough prompt tokens\n", __func__);
299        return 1;
300    }
301    tokens.resize(n_rs_seq + 1, tokens.back());
302
303    const uint32_t  n_tokens     = tokens.size();
304    const llama_pos rollback_pos = (llama_pos) n_tokens - n_rollback;
305
306    // Decode the full prompt on the source, then roll back three positions.
307    // Replaying them crosses DSV4's ratio-4 compressor boundary.
308    // Rollback leaves the recurrent memory in a snapshot state (rs_idx != 0).
309    if (!decode_tokens(ctx_src, tokens, n_tokens)) {
310        fprintf(stderr, "%s : failed to decode prompt\n", __func__);
311        return 1;
312    }
313    if (!llama_memory_seq_rm(llama_get_memory(ctx_src), 0, rollback_pos, -1)) {
314        fprintf(stderr, "%s : rollback failed\n", __func__);
315        return 1;
316    }
317
318    // Save the rolled-back state and restore it into a fresh context.
319    common_prompt_checkpoint ckpt;
320    ckpt.update_tgt(ctx_src, 0, 0);
321    ckpt.load_tgt(ctx_dst, 0, 0);
322
323    constexpr float eps = 1e-5f;
324    std::vector<std::vector<float>> logits_src_replay(n_rollback);
325    const auto replay_and_compare = [&](const char * mode) {
326        for (uint32_t i = 0; i < n_rollback; ++i) {
327            const llama_pos pos = rollback_pos + i;
328            if (!decode_one(ctx_src, tokens[pos], pos) ||
329                !decode_one(ctx_dst, tokens[pos], pos)) {
330                fprintf(stderr, "%s : %s replay failed at position %d\n", __func__, mode, pos);
331                return false;
332            }
333
334            const float * logits_src = llama_get_logits_ith(ctx_src, 0);
335            const float * logits_dst = llama_get_logits_ith(ctx_dst, 0);
336            if (logits_src == nullptr || logits_dst == nullptr) {
337                fprintf(stderr, "%s : missing %s logits at position %d\n", __func__, mode, pos);
338                return false;
339            }
340
341            logits_src_replay[i].assign(logits_src, logits_src + n_vocab);
342            for (int token = 0; token < n_vocab; ++token) {
343                if (logit_diff(logits_src[token], logits_dst[token]) > eps) {
344                    fprintf(stderr, "%s : %s logits mismatch at position %d, token %d (%g != %g)\n",
345                            __func__, mode, pos, token, (double) logits_src[token], (double) logits_dst[token]);
346                    return false;
347                }
348            }
349        }
350        return true;
351    };
352    if (!replay_and_compare("full")) {
353        return 1;
354    }
355
356    if (!llama_memory_seq_rm(llama_get_memory(ctx_src), 0, rollback_pos, -1) ||
357        !llama_memory_seq_rm(llama_get_memory(ctx_dst), 0, rollback_pos, -1)) {
358        fprintf(stderr, "%s : partial rollback failed\n", __func__);
359        return 1;
360    }
361
362    constexpr llama_state_seq_flags partial_flags = LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY;
363    common_prompt_checkpoint ckpt_partial;
364    ckpt_partial.update_tgt(ctx_src, 0, partial_flags);
365    ckpt_partial.load_tgt(ctx_dst, 0, partial_flags);
366
367    if (!replay_and_compare("partial")) {
368        return 1;
369    }
370
371    // Repeat the load into a context that already has its own rollback state:
372    // groups 1..n_rs_seq hold a different prompt's history, and rs_idx[0] is
373    // non-zero at load time. The restore must wipe that state and still match.
374    llama_context * ctx_dirty = make_ctx(params, model, fill);
375    if (ctx_dirty == nullptr) {
376        fprintf(stderr, "%s : failed to init dirty ctx\n", __func__);
377        return 1;
378    }
379
380    std::vector<llama_token> noise = tokens;
381    for (auto & t : noise) {
382        t = (t + 1) % n_vocab;
383        if (t < 0) {
384            t = 0;
385        }
386    }
387    if (!decode_tokens(ctx_dirty, noise, n_tokens)) {
388        fprintf(stderr, "%s : dirty prompt decode failed\n", __func__);
389        return 1;
390    }
391    if (!llama_memory_seq_rm(llama_get_memory(ctx_dirty), 0, rollback_pos, -1)) {
392        fprintf(stderr, "%s : dirty rollback failed\n", __func__);
393        return 1;
394    }
395
396    ckpt.load_tgt(ctx_dirty, 0, 0);
397
398    for (uint32_t i = 0; i < n_rollback; ++i) {
399        const llama_pos pos = rollback_pos + i;
400        if (!decode_one(ctx_dirty, tokens[pos], pos)) {
401            fprintf(stderr, "%s : dirty replay failed at position %d\n", __func__, pos);
402            return 1;
403        }
404
405        const float * logits_dirty = llama_get_logits_ith(ctx_dirty, 0);
406        if (logits_dirty == nullptr) {
407            fprintf(stderr, "%s : missing dirty logits at position %d\n", __func__, pos);
408            return 1;
409        }
410
411        for (int token = 0; token < n_vocab; ++token) {
412            if (logit_diff(logits_src_replay[i][token], logits_dirty[token]) > eps) {
413                fprintf(stderr, "%s : dirty-ctx logits mismatch at position %d, token %d (%g != %g)\n",
414                        __func__, pos, token, (double) logits_src_replay[i][token], (double) logits_dirty[token]);
415                return 1;
416            }
417        }
418    }
419
420    fprintf(stderr, "%s : recurrent rollback checkpoint restored successfully\n", __func__);
421    llama_free(ctx_src);
422    llama_free(ctx_dst);
423    llama_free(ctx_dirty);
424
425    if (!test_multi_seq_split_replay(params, model, n_vocab, fill)) {
426        return 1;
427    }
428
429    return 0;
430}
431
432int main(int argc, char ** argv) {
433    std::setlocale(LC_NUMERIC, "C");
434
435    common_params params;
436    params.sampling.seed = 1234;
437    params.n_predict = 1;
438
439    common_init();
440
441    if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_COMMON)) {
442        return 1;
443    }
444
445    ggml_backend_load_all();
446
447    common_init_result_ptr llama_init = common_init_from_params(params);
448    llama_model * model = llama_init->model();
449    if (model == nullptr) {
450        fprintf(stderr, "%s : failed to init model\n", __func__);
451        return 1;
452    }
453
454    if (!llama_model_is_recurrent(model) && !llama_model_is_hybrid(model)) {
455        fprintf(stderr, "%s : skipping for non-recurrent model\n", __func__);
456        return 0;
457    }
458
459    for (uint8_t fill : { 0, 0x3e }) {
460        fprintf(stderr, "%s : testing with cache fill 0x%02x\n", __func__, fill);
461        if (test_rollback(params, model, fill) != 0) {
462            return 1;
463        }
464    }
465
466    return 0;
467}