char-fork/llama.cpp

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

Jim Wutests : disable backend sampler hip multi output (#26878)4801e3c56

char/aliceai-t5-moe
86.6 KiB2167 linesraw
1#include "ggml.h"
2#include "llama.h"
3#include "llama-cpp.h"
4#include "common.h"
5
6#ifdef NDEBUG
7#undef NDEBUG
8#endif
9
10#include <algorithm>
11#include <cmath>
12#include <cstdlib>
13#include <cstring>
14#include <fstream>
15#include <functional>
16#include <map>
17#include <random>
18#include <string>
19#include <unordered_map>
20#include <unordered_set>
21#include <vector>
22
23struct test_args {
24    std::string model;
25    std::string test;
26    std::string device = "auto";
27};
28
29struct test_params {
30    llama_model_ptr model;
31};
32
33static llama_model_ptr load_model(const test_args & args) {
34    auto mparams = llama_model_default_params();
35
36    ggml_backend_dev_t devs[2] = { nullptr, nullptr };
37
38    if (args.device != "auto") {
39        if (args.device == "gpu") {
40            devs[0] = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU);
41
42            if (devs[0] == nullptr) {
43                fprintf(stderr, "Error: GPU requested but not available\n");
44                return nullptr;
45            }
46
47            mparams.n_gpu_layers = 999;
48        } else if (args.device == "cpu") {
49            devs[0] = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
50
51            mparams.n_gpu_layers = 0;
52        } else {
53            fprintf(stderr, "Error: invalid device '%s'\n", args.device.c_str());
54            return nullptr;
55        }
56
57        mparams.devices = devs;
58
59        fprintf(stderr, "Using device: %s\n", ggml_backend_dev_name(devs[0]));
60    }
61
62    llama_model_ptr res;
63
64    res.reset(llama_model_load_from_file(args.model.c_str(), mparams));
65
66    if (!res) {
67        fprintf(stderr, "Warning: failed to load model '%s', skipping test\n", args.model.c_str());
68        return nullptr;
69    }
70
71    return res;
72}
73
74struct test_context {
75    llama_context_ptr ctx;
76
77    int n_vocab = 0;
78
79    const llama_vocab * vocab = nullptr;
80
81    std::unordered_map<llama_seq_id, int32_t> seq_positions;
82    std::unordered_map<llama_seq_id, int32_t> last_batch_info;
83
84    test_context(
85            const test_params & params,
86            std::vector<llama_sampler_seq_config> & configs,
87            int32_t n_seq_max = -1,
88            uint32_t n_outputs_max = 0,
89            uint32_t n_ubatch = 0,
90            uint32_t n_outputs_max_per_seq = 1) {
91        auto * model = params.model.get();
92
93        GGML_ASSERT(model);
94        GGML_ASSERT(!ctx);
95
96        llama_context_params cparams = llama_context_default_params();
97        cparams.n_ctx = 512;
98        cparams.n_batch = 512;
99        if (n_ubatch > 0) {
100            cparams.n_ubatch = n_ubatch;
101        }
102        cparams.n_outputs_max = n_outputs_max;
103        cparams.n_outputs_max_per_seq = n_outputs_max_per_seq;
104        cparams.samplers = configs.data();
105        cparams.n_samplers = configs.size();
106        cparams.kv_unified = true;
107
108        // If n_seq_max is not specified, calculate it from configs
109        if (n_seq_max < 0) {
110            int32_t max_seq_id = 0;
111            for (const auto & config : configs) {
112                max_seq_id = std::max(config.seq_id, max_seq_id);
113            }
114            cparams.n_seq_max = max_seq_id + 1;
115        } else {
116            cparams.n_seq_max = n_seq_max;
117        }
118
119        ctx.reset(llama_init_from_model(model, cparams));
120        if (!ctx) {
121            throw std::runtime_error("failed to create context");
122        }
123
124        vocab = llama_model_get_vocab(model);
125        n_vocab = llama_vocab_n_tokens(vocab);
126    }
127
128    bool decode(const std::map<llama_seq_id, std::string> & prompts) {
129        GGML_ASSERT(ctx);
130
131        last_batch_info.clear();
132        llama_batch batch = llama_batch_init(512, 0, prompts.size());
133
134        for (const auto & [seq_id, prompt] : prompts) {
135            std::vector<llama_token> tokens;
136            tokens.push_back(llama_vocab_bos(vocab));
137
138            std::vector<llama_token> prompt_tokens(32);
139            int n_tokens = llama_tokenize(vocab, prompt.c_str(), prompt.length(),
140                                           prompt_tokens.data(), prompt_tokens.size(),
141                                           false, false);
142            if (n_tokens < 0) {
143                fprintf(stderr, "Warning: tokenization failed for seq_id %d\n", seq_id);
144                llama_batch_free(batch);
145                return false;
146            }
147
148            for (int i = 0; i < n_tokens; i++) {
149                tokens.push_back(prompt_tokens[i]);
150            }
151
152            if (seq_positions.find(seq_id) == seq_positions.end()) {
153                seq_positions[seq_id] = 0;
154            }
155
156            int32_t start_pos = seq_positions[seq_id];
157            for (size_t i = 0; i < tokens.size(); i++) {
158                common_batch_add(batch, tokens[i], start_pos + i, { seq_id }, i == tokens.size() - 1);
159            }
160
161            seq_positions[seq_id] = start_pos + tokens.size();
162        }
163
164
165        printf("Batch contents:\n");
166        printf("n_tokens: %d\n", batch.n_tokens);
167        for (int i = 0; i < batch.n_tokens; i++) {
168            printf("token[%d]: tok=%-5d, pos=%d, n_seq_id=%d, seq_ids=[", i, batch.token[i], batch.pos[i], batch.n_seq_id[i]);
169
170            for (int j = 0; j < batch.n_seq_id[i]; j++) {
171                printf("%d%s", batch.seq_id[i][j], j < batch.n_seq_id[i]-1 ? ", " : "");
172            }
173            printf("], logits=%d\n", batch.logits[i]);
174        }
175
176        if (llama_decode(ctx.get(), batch) != 0) {
177            fprintf(stderr, "Warning: llama_decode failed\n");
178            llama_batch_free(batch);
179            return false;
180        }
181
182        // Build mapping from seq id to batch token idx
183        for (int i = 0; i < batch.n_tokens; i++) {
184            if (batch.logits[i]) {
185                llama_seq_id seq_id = batch.seq_id[i][0];
186                last_batch_info[seq_id] = i;
187            }
188        }
189
190        llama_batch_free(batch);
191        return true;
192    }
193
194    int32_t idx_for_seq(llama_seq_id seq_id) {
195        auto it = last_batch_info.find(seq_id);
196        if (it == last_batch_info.end()) {
197            fprintf(stderr, "Error: no batch index found for seq_id %d\n", seq_id);
198            return -1;
199        }
200        return it->second;
201    }
202
203    void update_batch_info(const llama_batch & batch) {
204        last_batch_info.clear();
205        for (int i = 0; i < batch.n_tokens; i++) {
206            if (batch.logits[i]) {
207                llama_seq_id cur_seq = batch.seq_id[i][0];
208                last_batch_info[cur_seq] = i;
209            }
210        }
211    }
212
213    bool decode_token(llama_token token, llama_seq_id seq_id = 0) {
214        GGML_ASSERT(ctx);
215
216        llama_batch batch = llama_batch_init(1, 0, 1);
217        int32_t pos = seq_positions[seq_id];
218        common_batch_add(batch, token, pos, { seq_id }, true);
219
220        if (llama_decode(ctx.get(), batch) != 0) {
221            fprintf(stderr, "Warning: llama_decode failed for token %d in seq %d\n", token, seq_id);
222            llama_batch_free(batch);
223            return false;
224        }
225
226        update_batch_info(batch);
227
228        seq_positions[seq_id]++;
229        llama_batch_free(batch);
230
231        return true;
232    }
233
234    bool decode_tokens(const std::map<llama_seq_id, llama_token> & seq_tokens) {
235        GGML_ASSERT(ctx);
236
237        llama_batch batch = llama_batch_init(seq_tokens.size(), 0, seq_tokens.size());
238
239        for (const auto & [seq_id, token] : seq_tokens) {
240            int32_t pos = seq_positions[seq_id];
241            common_batch_add(batch, token, pos, { seq_id }, true);
242        }
243
244        if (llama_decode(ctx.get(), batch) != 0) {
245            fprintf(stderr, "Warning: llama_decode failed for batch tokens\n");
246            llama_batch_free(batch);
247            return false;
248        }
249
250        for (const auto & [seq_id, _] : seq_tokens) {
251            seq_positions[seq_id]++;
252        }
253
254        update_batch_info(batch);
255
256        llama_batch_free(batch);
257
258        return true;
259    }
260
261    std::string token_to_piece(llama_token token, bool special) const {
262        std::string piece;
263        piece.resize(piece.capacity());  // using string internal cache, 15 bytes + '\n'
264        const int n_chars = llama_token_to_piece(vocab, token, &piece[0], piece.size(), 0, special);
265        if (n_chars < 0) {
266            piece.resize(-n_chars);
267            int check = llama_token_to_piece(vocab, token, &piece[0], piece.size(), 0, special);
268            GGML_ASSERT(check == -n_chars);
269        } else {
270            piece.resize(n_chars);
271        }
272
273        return piece;
274    }
275};
276
277struct test_single_output_backend_sampler {
278    bool backend_initialized = false;
279    uint32_t backend_outputs_max_per_seq = 0;
280    int backend_apply_count = 0;
281    int apply_count = 0;
282};
283
284static const char * test_single_output_backend_sampler_name(const llama_sampler * /*smpl*/) {
285    return "single-output-backend";
286}
287
288static void test_single_output_backend_sampler_apply(
289        llama_sampler * smpl, llama_token_data_array * /*cur_p*/) {
290    auto * ctx = (test_single_output_backend_sampler *) smpl->ctx;
291    ctx->apply_count++;
292}
293
294static void test_single_output_backend_sampler_free(llama_sampler * smpl) {
295    delete (test_single_output_backend_sampler *) smpl->ctx;
296}
297
298static bool test_single_output_backend_sampler_backend_init(
299        llama_sampler * smpl, ggml_backend_buffer_type_t /*buft*/, uint32_t n_outputs_max_per_seq) {
300    auto * ctx = (test_single_output_backend_sampler *) smpl->ctx;
301    ctx->backend_outputs_max_per_seq = n_outputs_max_per_seq;
302    if (n_outputs_max_per_seq > 1) {
303        return false;
304    }
305    ctx->backend_initialized = true;
306    return true;
307}
308
309static void test_single_output_backend_sampler_backend_apply(
310        llama_sampler * smpl, ggml_context * /*ctx*/, ggml_cgraph * /*gf*/, llama_sampler_data * /*data*/) {
311    auto * ctx = (test_single_output_backend_sampler *) smpl->ctx;
312    ctx->backend_apply_count++;
313}
314
315static llama_sampler_i test_single_output_backend_sampler_i = {
316    /* .name              = */ test_single_output_backend_sampler_name,
317    /* .accept            = */ nullptr,
318    /* .apply             = */ test_single_output_backend_sampler_apply,
319    /* .reset             = */ nullptr,
320    /* .clone             = */ nullptr,
321    /* .free              = */ test_single_output_backend_sampler_free,
322    /* .backend_init      = */ test_single_output_backend_sampler_backend_init,
323    /* .backend_accept    = */ nullptr,
324    /* .backend_apply     = */ test_single_output_backend_sampler_backend_apply,
325    /* .backend_set_input = */ nullptr,
326    /* .backend_reset     = */ nullptr,
327    /* .copy_state        = */ nullptr,
328};
329
330static llama_sampler * test_single_output_backend_sampler_init(
331        test_single_output_backend_sampler ** sampler_ctx) {
332    auto * ctx = new test_single_output_backend_sampler;
333    *sampler_ctx = ctx;
334    return llama_sampler_init(&test_single_output_backend_sampler_i, ctx);
335}
336
337static void test_backend_greedy_sampling(const test_params & params) {
338    const int seq_id = 0;
339
340    struct llama_sampler_chain_params backend_sampler_params = llama_sampler_chain_default_params();
341    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_sampler_params));
342
343    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_greedy());
344    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
345
346    test_context test_ctx(params, backend_sampler_configs);
347
348    if (!test_ctx.decode({{seq_id, "Some"}})) {
349        GGML_ASSERT(false && "Failed to decode token");
350    }
351
352    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
353
354    llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
355    printf("greedy sampled id:%d, string:'%s'\n", token, test_ctx.token_to_piece(token, false).c_str());
356    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
357
358    token = llama_get_sampled_token_ith(test_ctx.ctx.get(), -1);
359    printf("greedy sampled id:%d, string:'%s'\n", token, test_ctx.token_to_piece(token, false).c_str());
360    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
361
362    for (int i = 0; i < 10; i++) {
363        int32_t loop_idx = test_ctx.idx_for_seq(seq_id);
364        llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), loop_idx);
365        printf("Generation step %d: token id:%d, string: %s\n", i, token, test_ctx.token_to_piece(token, false).c_str());
366        if (!test_ctx.decode_token(token, 0)) {
367            GGML_ASSERT(false && "Failed to decode token");
368        }
369    }
370}
371
372static void test_backend_top_k_sampling(const test_params & params) {
373    const int seq_id = 0;
374    const int32_t k = 8;
375    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
376    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
377    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_top_k(k));
378    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
379
380    test_context test_ctx(params, backend_sampler_configs);
381
382    if (!test_ctx.decode({{seq_id, "Hello"}})) {
383        GGML_ASSERT(false && "Failed to decode token");
384    }
385
386    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
387
388    float * logits = llama_get_sampled_logits_ith(test_ctx.ctx.get(), batch_idx);
389    uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
390    for (size_t i = 0; i < n_logits; ++i) {
391        printf("top_k logit[%zu] = %.6f\n", i, logits[i]);
392    }
393
394    llama_token * candidates = llama_get_sampled_candidates_ith(test_ctx.ctx.get(), batch_idx);
395    uint32_t n_candidates = llama_get_sampled_candidates_count_ith(test_ctx.ctx.get(), batch_idx);
396    for (size_t i = 0; i < n_candidates; ++i) {
397        printf("top_k candidate[%zu] = %d : %s\n", i, candidates[i],
398               test_ctx.token_to_piece(candidates[i], false).c_str());
399    }
400
401    // Sample using CPU sampler for verification that it is possible to do hybrid
402    // sampling, first top_k on the backend and then dist on the CPU.
403    struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
404    llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
405    GGML_ASSERT(chain->iface->backend_apply != nullptr);
406
407    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(18));
408    llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
409    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
410
411    printf("backend top-k hybrid sampling test PASSED\n");
412}
413
414static void test_backend_temp_sampling(const test_params & params) {
415    {
416        const float temp_0 = 0.8f;
417        struct llama_sampler_chain_params backend_chain_params_0 = llama_sampler_chain_default_params();
418        llama_sampler_ptr backend_sampler_chain_0(llama_sampler_chain_init(backend_chain_params_0));
419        llama_sampler_chain_add(backend_sampler_chain_0.get(), llama_sampler_init_temp(temp_0));
420
421        const float temp_1 = 0.1f;
422        struct llama_sampler_chain_params backend_chain_params_1 = llama_sampler_chain_default_params();
423        llama_sampler_ptr backend_sampler_chain_1(llama_sampler_chain_init(backend_chain_params_1));
424        llama_sampler_chain_add(backend_sampler_chain_1.get(), llama_sampler_init_temp(temp_1));
425
426        std::vector<llama_sampler_seq_config> backend_sampler_configs = {
427            { 0, backend_sampler_chain_0.get() },
428            { 1, backend_sampler_chain_1.get() }
429        };
430
431        test_context test_ctx(params, backend_sampler_configs);
432
433        if (!test_ctx.decode({{0, "Some where over the"}, {1, "Once upon a"}})) {
434            GGML_ASSERT(false && "Failed to decode token");
435        }
436
437        // Verify sequence 0
438        {
439            int32_t batch_idx = test_ctx.idx_for_seq(0);
440            int n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
441            GGML_ASSERT(n_logits == test_ctx.n_vocab);
442
443            // Sample from sequence 0 using CPU sampler
444            struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
445            llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
446            llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(18));
447
448            llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
449            const std::string token_str = test_ctx.token_to_piece(token, false);
450            printf("Sequence 0 sampled token id:%d, string: '%s'\n", token, token_str.c_str());
451            GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
452        }
453
454
455        // Verify sequence 1
456        {
457            int32_t batch_idx = test_ctx.idx_for_seq(1);
458
459            // Sample from sequence 1 using CPU sampler
460            struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
461            llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
462            llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(18));
463
464            llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
465            const std::string token_str = test_ctx.token_to_piece(token, false);
466            printf("Sequence 1 sampled token id:%d, string: '%s'\n", token, token_str.c_str());
467            GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
468        }
469    }
470
471    // lambda for testing non-positive temperature values.
472    auto test_argmax_temp = [&](float temp) {
473        printf("\nTesting temperature = %.1f\n", temp);
474
475        int seq_id = 0;
476        struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
477        llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
478        llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_temp(temp));
479
480        std::vector<llama_sampler_seq_config> backend_sampler_configs = {
481            { seq_id, backend_sampler_chain.get() },
482        };
483
484        test_context test_ctx(params, backend_sampler_configs);
485
486        if (!test_ctx.decode({{seq_id, "Once"}})) {
487            GGML_ASSERT(false && "Failed to decode token");
488        }
489
490        int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
491
492        uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
493        GGML_ASSERT(n_logits == 1);
494    };
495
496    test_argmax_temp(0.0f);
497    test_argmax_temp(-1.0f);
498
499    printf("backend temp sampling test PASSED\n");
500}
501
502static void test_backend_temp_ext_sampling(const test_params & params) {
503    {
504        int seq_id = 0;
505        const float temp = 0.8f;
506        const float delta = 0.5f;
507        const float exponent = 1.5f;
508        struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
509        llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
510        llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_temp_ext(temp, delta, exponent));
511
512        std::vector<llama_sampler_seq_config> backend_sampler_configs = {
513            { seq_id, backend_sampler_chain.get() },
514        };
515
516        test_context test_ctx(params, backend_sampler_configs);
517
518        if (!test_ctx.decode({{seq_id, "Once upon a"}})) {
519            GGML_ASSERT(false && "Failed to decode token");
520        }
521
522        // Verify sequence 0
523        {
524            int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
525            int n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
526            GGML_ASSERT(n_logits == test_ctx.n_vocab);
527        }
528    }
529
530    // lambda for testing non-positive temp/delta/exponent values.
531    auto test_argmax_temp = [&](float temp, float delta, float exponent) {
532        printf("\nTesting temperature = %.1f, delta = %1.f, exponent = %1.f\n", temp, delta, exponent);
533
534        int seq_id = 0;
535        struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
536        llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
537        llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_temp_ext(temp, delta, exponent));
538
539        std::vector<llama_sampler_seq_config> backend_sampler_configs = {
540            { seq_id, backend_sampler_chain.get() },
541        };
542
543        test_context test_ctx(params, backend_sampler_configs);
544
545        if (!test_ctx.decode({{seq_id, "Once"}})) {
546            GGML_ASSERT(false && "Failed to decode token");
547        }
548
549        int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
550
551        uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
552
553        if (temp <= 0.0f && delta >= 0.0f) {
554            GGML_ASSERT(n_logits == 1);
555        } else {
556            GGML_ASSERT(n_logits == (uint32_t) test_ctx.n_vocab);
557        }
558    };
559
560    test_argmax_temp(0.0f,  0.3f, 1.0f); // Greedy (temp=0)
561    test_argmax_temp(-1.0f, 0.3f, 2.0f); // Greedy (temp<0)
562    test_argmax_temp(0.8f,  0.0f, 2.0f); // Temperature scaling
563
564    printf("backend temp_ext sampling test PASSED\n");
565}
566
567static void test_backend_min_p_sampling(const test_params & params) {
568    const int seq_id = 0;
569    const float p = 0.1;
570    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
571    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
572    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_min_p(p, 0));
573    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
574
575    test_context test_ctx(params, backend_sampler_configs);
576
577    if (!test_ctx.decode({{seq_id, "Hello"}})) {
578        GGML_ASSERT(false && "Failed to decode token");
579    }
580
581    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
582
583    float * logits = llama_get_sampled_logits_ith(test_ctx.ctx.get(), batch_idx);
584    uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
585
586    // Print the logits that are above the min-p threshold
587    std::vector<float> filtered_logits;
588    for (size_t i = 0; i < n_logits; ++i) {
589        if (logits[i] > -1e9f) {
590            filtered_logits.push_back(logits[i]);
591            //printf("min_p logit[%zu] = %.6f\n", i, logits[i]);
592        }
593    }
594    GGML_ASSERT(filtered_logits.size() < (size_t) test_ctx.n_vocab);
595
596    // Sample using CPU sampler for verification to inspect they are reasonable
597    struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
598    llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
599    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(88));
600
601    llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
602    const std::string token_str = test_ctx.token_to_piece(token, false);
603    printf("min-p cpu sampled token id:%d, string: '%s'\n", token, token_str.c_str());
604    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
605
606    // Decode and sample 10 more tokens
607    for (int i = 0; i < 10; i++) {
608        int32_t loop_idx = test_ctx.idx_for_seq(seq_id);
609        llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), loop_idx);
610        printf("min-p gen step %d: token id :%5.d, string: %s\n", i, token, test_ctx.token_to_piece(token, false).c_str());
611        if (!test_ctx.decode_token(token, 0)) {
612            GGML_ASSERT(false && "Failed to decode token");
613        }
614    }
615
616    printf("min-p sampling test PASSED\n");
617}
618
619static void test_backend_top_p_sampling(const test_params & params) {
620    const int seq_id = 0;
621    const float p = 0.9;
622    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
623    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
624    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_top_p(p, 0));
625    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
626
627    test_context test_ctx(params, backend_sampler_configs);
628
629    if (!test_ctx.decode({{seq_id, "Hello"}})) {
630        return;
631    }
632
633    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
634
635    float * logits = llama_get_sampled_logits_ith(test_ctx.ctx.get(), batch_idx);
636    uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
637
638    // Print the logits that are above the min-p threshold
639    std::vector<float> filtered_logits;
640    for (size_t i = 0; i < n_logits; ++i) {
641        if (logits[i] > -1e9f) {
642            filtered_logits.push_back(logits[i]);
643        }
644    }
645    GGML_ASSERT(filtered_logits.size() < (size_t) test_ctx.n_vocab);
646    GGML_ASSERT(filtered_logits.size() > 0);
647
648    // Sample using CPU sampler for verification to inspect they are reasonable
649    struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
650    llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
651    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(88));
652
653    llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
654    const std::string token_str = test_ctx.token_to_piece(token, false);
655    printf("top-p cpu sampled token id:%d, string: '%s'\n", token, token_str.c_str());
656    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
657
658    // Decode and sample 10 more tokens
659    for (int i = 0; i < 10; i++) {
660        int32_t loop_idx = test_ctx.idx_for_seq(seq_id);
661        llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), loop_idx);
662        printf("top-p gen step %d: token id :%5.d, string: %s\n", i, token, test_ctx.token_to_piece(token, false).c_str());
663        test_ctx.decode_token(token, 0);
664    }
665
666    printf("top-p sampling test PASSED\n");
667}
668
669static void test_backend_multi_sequence_sampling(const test_params & params) {
670    struct llama_sampler_chain_params chain_params_0 = llama_sampler_chain_default_params();
671    llama_sampler_ptr sampler_chain_0(llama_sampler_chain_init(chain_params_0));
672    llama_sampler_chain_add(sampler_chain_0.get(), llama_sampler_init_greedy());
673
674    struct llama_sampler_chain_params chain_params_1 = llama_sampler_chain_default_params();
675    llama_sampler_ptr sampler_chain_1(llama_sampler_chain_init(chain_params_1));
676    llama_sampler_chain_add(sampler_chain_1.get(), llama_sampler_init_temp(0.8f));
677    llama_sampler_chain_add(sampler_chain_1.get(), llama_sampler_init_greedy());
678
679    std::vector<llama_sampler_seq_config> backend_sampler_configs = {
680        { 0, sampler_chain_0.get() },
681        { 1, sampler_chain_1.get() }
682    };
683
684    test_context test_ctx(params, backend_sampler_configs);
685
686    std::map<llama_seq_id, std::string> prompts = {
687        {0, "Hello"},
688        {1, "Some"}
689    };
690
691    if (!test_ctx.decode(prompts)) {
692        GGML_ASSERT(false && "Failed to decode token");
693    }
694
695    // Verify sequence 0
696    {
697        int32_t batch_idx = test_ctx.idx_for_seq(0);
698        llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
699        const std::string token_str = test_ctx.token_to_piece(token, false);
700        printf("Seq 0 sampled token id=%d, string='%s'\n", token, token_str.c_str());
701        GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
702    }
703
704    // Verify sequence 1
705    {
706        int32_t batch_idx= test_ctx.idx_for_seq(1);
707        llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
708        const std::string token_str = test_ctx.token_to_piece(token, false);
709        printf("Seq 1 sampled token id=%d, string='%s'\n", token, token_str.c_str());
710        GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
711    }
712
713    // Generate tokens for each sequence
714    printf("\nMulti-sequence generation:\n");
715    for (int step = 0; step < 4; step++) {
716        std::map<llama_seq_id, llama_token> tokens;
717
718        for (llama_seq_id seq_id : {0, 1}) {
719            int32_t idx = test_ctx.idx_for_seq(seq_id);
720            llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), idx);
721            const std::string token_str = test_ctx.token_to_piece(token, false);
722            printf("  Seq %d, step %d: token id=%d, string='%s'\n", seq_id, step, token, token_str.c_str());
723            tokens[seq_id] = token;
724        }
725
726        // Decode all tokens in a single batch
727        if (!test_ctx.decode_tokens(tokens)) {
728            GGML_ASSERT(false && "Failed to decode token");
729        }
730    }
731
732    printf("backend multi-sequence sampling test PASSED\n");
733}
734
735static void test_backend_dist_sampling(const test_params & params) {
736    const int seq_id = 0;
737    const int32_t seed = 88;
738
739    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
740    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
741    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed));
742    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
743
744    test_context test_ctx(params, backend_sampler_configs);
745
746    if (!test_ctx.decode({{seq_id, "Some"}})) {
747        GGML_ASSERT(false && "Failed to decode token");
748    }
749
750    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
751    llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
752    printf("dist sampled id:%d, string:'%s'\n", token, test_ctx.token_to_piece(token, false).c_str());
753    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
754    //GGML_ASSERT(llama_get_sampled_logits_ith(test_ctx.ctx.get(), batch_idx) == nullptr);
755
756    token = llama_get_sampled_token_ith(test_ctx.ctx.get(), -1);
757    printf("dist sampled id:%d, string:'%s'\n", token, test_ctx.token_to_piece(token, false).c_str());
758    GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
759
760    printf("backend dist sampling test PASSED\n");
761}
762
763static void test_backend_dist_sampling_and_cpu(const test_params & params) {
764    const int seq_id = 0;
765    const int32_t seed = 88;
766
767    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
768    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
769    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed));
770    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
771
772    test_context test_ctx(params, backend_sampler_configs);
773
774    if (!test_ctx.decode({{seq_id, "Some"}})) {
775        GGML_ASSERT(false && "Failed to decode token");
776    }
777
778    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
779
780    // Sample using CPU sampler
781    struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
782    llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
783    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(18));
784
785    llama_token backend_token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
786    llama_token cpu_token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
787    printf("dist & cpu sampled id:%d, string:'%s'\n", cpu_token, test_ctx.token_to_piece(cpu_token, false).c_str());
788    GGML_ASSERT(backend_token == cpu_token);
789
790    printf("backend dist & cpu sampling test PASSED\n");
791}
792
793static void test_backend_logit_bias_sampling(const test_params & params) {
794    const auto * model = params.model.get();
795    const auto * vocab = llama_model_get_vocab(model);
796
797    const int seq_id = 0;
798
799    std::vector<llama_logit_bias> logit_bias;
800
801    // Get the token for the piece "World".
802    const std::string piece = "World";
803    std::vector<llama_token> tokens(16);
804    llama_tokenize(vocab, piece.c_str(), piece.size(), tokens.data(), tokens.size(), false, false);
805
806    llama_token bias_token = tokens[0];
807    // TODO: biasing too much here makes the Vulkan sampling fail - should be investigated further
808    //       https://github.com/ggml-org/llama.cpp/actions/runs/20894267644/job/60030252675?pr=18753#step:3:23350
809    //logit_bias.push_back({ bias_token, +100.0f });
810    logit_bias.push_back({ bias_token, +10.0f });
811
812    printf("biasing token piece '%s' -> token id %d\n", piece.c_str(), bias_token);
813
814    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
815    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
816    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_logit_bias(
817                llama_vocab_n_tokens(vocab),
818                logit_bias.size(),
819                logit_bias.data()));
820    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(88));
821
822    std::vector<llama_sampler_seq_config> backend_sampler_configs = {
823        { seq_id, backend_sampler_chain.get() },
824    };
825
826    test_context test_ctx(params, backend_sampler_configs);
827
828    if (!test_ctx.decode({{seq_id, "Hello"}})) {
829        GGML_ASSERT(false && "Failed to decode token");
830    }
831
832    llama_token backend_token = llama_get_sampled_token_ith(test_ctx.ctx.get(), test_ctx.idx_for_seq(seq_id));
833    printf("sampled token = %d, expected = %d\n", backend_token, bias_token);
834    GGML_ASSERT(backend_token == bias_token);
835
836    printf("backend logit bias sampling test PASSED\n");
837}
838
839static void accept_prompt(llama_sampler * smpl, const llama_vocab * vocab, const std::string & prompt) {
840    const llama_token bos = llama_vocab_bos(vocab);
841    if (bos != LLAMA_TOKEN_NULL) {
842        llama_sampler_accept(smpl, bos);
843    }
844
845    std::vector<llama_token> tokens(64);
846    int32_t n_tokens = llama_tokenize(vocab, prompt.c_str(), (int32_t) prompt.size(),
847            tokens.data(), (int32_t) tokens.size(), false, false);
848    if (n_tokens < 0) {
849        tokens.resize(-n_tokens);
850        n_tokens = llama_tokenize(vocab, prompt.c_str(), (int32_t) prompt.size(),
851                tokens.data(), (int32_t) tokens.size(), false, false);
852    }
853
854    for (int32_t i = 0; i < n_tokens; ++i) {
855        llama_sampler_accept(smpl, tokens[i]);
856    }
857}
858
859static std::vector<float> decode_raw_logits(const test_params & params, const std::string & prompt) {
860    const int seq_id = 0;
861    const int n_vocab = llama_vocab_n_tokens(llama_model_get_vocab(params.model.get()));
862    std::vector<llama_sampler_seq_config> empty_configs;
863    test_context ctx(params, empty_configs);
864
865    GGML_ASSERT(ctx.decode({{ seq_id, prompt }}));
866
867    float * logits = llama_get_logits_ith(ctx.ctx.get(), ctx.idx_for_seq(seq_id));
868    GGML_ASSERT(logits != nullptr);
869    return std::vector<float>(logits, logits + n_vocab);
870}
871
872static std::vector<llama_token_data> apply_cpu_sampler(
873        const std::vector<float> & raw_logits,
874        llama_sampler * sampler) {
875    std::vector<llama_token_data> data;
876    data.reserve(raw_logits.size());
877    for (llama_token token = 0; token < (llama_token) raw_logits.size(); ++token) {
878        data.push_back({ token, raw_logits[token], 0.0f });
879    }
880
881    llama_token_data_array cur_p = { data.data(), data.size(), -1, false };
882    llama_sampler_apply(sampler, &cur_p);
883    data.resize(cur_p.size);
884    return data;
885}
886
887using sampler_setup_fn = std::function<void(llama_sampler *)>;
888using sampler_init_fn = std::function<llama_sampler *()>;
889
890enum class penalties_position {
891    before_filter,
892    after_filter,
893};
894
895static void add_filter_and_penalties(
896        llama_sampler * chain,
897        const sampler_init_fn & init_filter,
898        int32_t n_vocab,
899        int32_t penalty_last_n,
900        float penalty_repeat,
901        float penalty_freq,
902        float penalty_present,
903        penalties_position position) {
904    const auto add_penalties = [&]() {
905        llama_sampler_chain_add(chain, llama_sampler_init_penalties(
906                    n_vocab, penalty_last_n, penalty_repeat, penalty_freq, penalty_present));
907    };
908
909    if (position == penalties_position::before_filter) {
910        add_penalties();
911        llama_sampler_chain_add(chain, init_filter());
912    } else {
913        llama_sampler_chain_add(chain, init_filter());
914        add_penalties();
915    }
916}
917
918static llama_sampler_ptr make_sampler_chain(
919        const sampler_setup_fn & add_samplers,
920        const sampler_setup_fn & accept_history) {
921    llama_sampler_ptr chain(llama_sampler_chain_init(llama_sampler_chain_default_params()));
922    add_samplers(chain.get());
923    accept_history(chain.get());
924    return chain;
925}
926
927struct backend_sampler_output {
928    std::vector<float> logits;
929    std::vector<llama_token> candidates;
930};
931
932static backend_sampler_output run_backend_sampler(
933        const test_params & params,
934        const std::string & prompt,
935        llama_sampler * sampler) {
936    const int seq_id = 0;
937    std::vector<llama_sampler_seq_config> configs = {{ seq_id, sampler }};
938    test_context ctx(params, configs);
939
940    GGML_ASSERT(ctx.decode({{ seq_id, prompt }}));
941    llama_synchronize(ctx.ctx.get());
942
943    const int32_t idx = ctx.idx_for_seq(seq_id);
944    const uint32_t n_logits = llama_get_sampled_logits_count_ith(ctx.ctx.get(), idx);
945    const uint32_t n_candidates = llama_get_sampled_candidates_count_ith(ctx.ctx.get(), idx);
946    float * logits = llama_get_sampled_logits_ith(ctx.ctx.get(), idx);
947    llama_token * candidates = llama_get_sampled_candidates_ith(ctx.ctx.get(), idx);
948    GGML_ASSERT(logits != nullptr);
949
950    backend_sampler_output result;
951    result.logits.assign(logits, logits + n_logits);
952    result.candidates.resize(n_logits);
953
954    if (n_candidates == 0) {
955        for (uint32_t i = 0; i < n_logits; ++i) {
956            result.candidates[i] = (llama_token) i;
957        }
958    } else {
959        GGML_ASSERT(candidates != nullptr);
960        GGML_ASSERT(n_candidates == n_logits);
961        std::memcpy(result.candidates.data(), candidates, n_candidates * sizeof(llama_token));
962    }
963
964    return result;
965}
966
967struct sampler_comparison_output {
968    std::vector<llama_token_data> expected;
969    backend_sampler_output actual;
970};
971
972static sampler_comparison_output run_sampler_comparison(
973        const test_params & params,
974        const std::string & prompt,
975        const std::vector<float> & raw_logits,
976        const sampler_setup_fn & add_samplers,
977        const sampler_setup_fn & accept_history) {
978    llama_sampler_ptr cpu_chain = make_sampler_chain(add_samplers, accept_history);
979    llama_sampler_ptr backend_chain = make_sampler_chain(add_samplers, accept_history);
980    return {
981        apply_cpu_sampler(raw_logits, cpu_chain.get()),
982        run_backend_sampler(params, prompt, backend_chain.get()),
983    };
984}
985
986static std::unordered_map<llama_token, float> map_logits(const std::vector<llama_token_data> & data) {
987    std::unordered_map<llama_token, float> result;
988    result.reserve(data.size());
989    for (const auto & item : data) {
990        result[item.id] = item.logit;
991    }
992    return result;
993}
994
995struct sampler_comparison_stats {
996    int n_mismatch = 0;
997    int n_masked = 0;
998    float max_diff = 0.0f;
999};
1000
1001static sampler_comparison_stats compare_sampler_outputs(
1002        const char * name,
1003        const std::unordered_map<llama_token, float> & expected,
1004        const backend_sampler_output & actual,
1005        bool allow_extra_candidates = false) {
1006    GGML_ASSERT(actual.logits.size() == actual.candidates.size());
1007
1008    sampler_comparison_stats result;
1009    std::unordered_set<llama_token> seen;
1010    seen.reserve(actual.candidates.size());
1011
1012    for (size_t i = 0; i < actual.logits.size(); ++i) {
1013        const llama_token token = actual.candidates[i];
1014        const float logit = actual.logits[i];
1015        if (!seen.insert(token).second || std::isnan(logit)) {
1016            if (result.n_mismatch < 5) {
1017                printf("%s token %d has invalid backend output\n", name, token);
1018            }
1019            ++result.n_mismatch;
1020            continue;
1021        }
1022
1023        const auto it = expected.find(token);
1024        if (it == expected.end()) {
1025            if (std::isinf(logit) && logit < 0.0f) {
1026                ++result.n_masked;
1027            } else if (!allow_extra_candidates) {
1028                if (result.n_mismatch < 5) {
1029                    printf("%s token %d was not masked\n", name, token);
1030                }
1031                ++result.n_mismatch;
1032            }
1033            continue;
1034        }
1035
1036        const float diff = fabsf(it->second - logit);
1037        result.max_diff = std::max(result.max_diff, diff);
1038        if (!std::isfinite(logit) || diff > 1e-3f) {
1039            if (result.n_mismatch < 5) {
1040                printf("%s mismatch token %d: cpu=%.6f backend=%.6f diff=%.6f\n",
1041                        name, token, it->second, logit, diff);
1042            }
1043            ++result.n_mismatch;
1044        }
1045    }
1046
1047    for (const auto & item : expected) {
1048        if (seen.find(item.first) == seen.end()) {
1049            if (result.n_mismatch < 5) {
1050                printf("%s missing backend token %d\n", name, item.first);
1051            }
1052            ++result.n_mismatch;
1053        }
1054    }
1055
1056    printf("%s logits: max_diff=%.6f n_masked=%d n_mismatch=%d\n",
1057            name, result.max_diff, result.n_masked, result.n_mismatch);
1058    return result;
1059}
1060
1061static float find_backend_logit(const backend_sampler_output & output, llama_token token) {
1062    for (size_t i = 0; i < output.candidates.size(); ++i) {
1063        if (output.candidates[i] == token) {
1064            return output.logits[i];
1065        }
1066    }
1067    GGML_ABORT("backend token not found");
1068}
1069
1070static sampler_comparison_output run_penalties_comparison(
1071        const test_params & params,
1072        int32_t penalty_last_n,
1073        float penalty_repeat,
1074        float penalty_freq,
1075        float penalty_present,
1076        const std::string & prompt,
1077        const std::function<void(llama_sampler *)> & extra_accept = {}) {
1078    const auto * vocab = llama_model_get_vocab(params.model.get());
1079    const std::vector<float> raw_logits = decode_raw_logits(params, prompt);
1080    const auto add_samplers = [&](llama_sampler * chain) {
1081        llama_sampler_chain_add(chain, llama_sampler_init_penalties(
1082                    llama_vocab_n_tokens(vocab), penalty_last_n, penalty_repeat, penalty_freq, penalty_present));
1083    };
1084    const auto accept_history = [&](llama_sampler * chain) {
1085        accept_prompt(chain, vocab, prompt);
1086        if (extra_accept) {
1087            extra_accept(chain);
1088        }
1089    };
1090
1091    return run_sampler_comparison(
1092            params, prompt, raw_logits, add_samplers, accept_history);
1093}
1094
1095static void compare_penalties_logits(
1096        const test_params & params,
1097        int32_t penalty_last_n,
1098        float penalty_repeat,
1099        float penalty_freq,
1100        float penalty_present,
1101        const std::string & prompt,
1102        const std::function<void(llama_sampler *)> & extra_accept = {}) {
1103    const sampler_comparison_output output = run_penalties_comparison(
1104            params, penalty_last_n, penalty_repeat, penalty_freq, penalty_present, prompt, extra_accept);
1105
1106    GGML_ASSERT(output.expected.size() == output.actual.logits.size());
1107
1108    const sampler_comparison_stats stats = compare_sampler_outputs(
1109            "penalties", map_logits(output.expected), output.actual);
1110    GGML_ASSERT(stats.n_masked == 0);
1111    GGML_ASSERT(stats.n_mismatch == 0);
1112}
1113
1114static void test_penalty_parameter_values(const test_params & params) {
1115    struct penalty_test_case {
1116        const char * name;
1117        float repeat;
1118        float frequency;
1119        float presence;
1120    };
1121
1122    const penalty_test_case cases[] = {
1123        { "frequency -1",   1.0f, -1.0f,     0.0f },
1124        { "frequency 0",    1.0f,  0.0f,     0.0f },
1125        { "frequency 1",    1.0f,  1.0f,     0.0f },
1126        { "presence -1",    1.0f,  0.0f,    -1.0f },
1127        { "presence 0",     1.0f,  0.0f,     0.0f },
1128        { "presence 1",     1.0f,  0.0f,     1.0f },
1129        { "repeat 1",       1.0f,  0.0f,     0.0f },
1130    };
1131
1132    int n_failed = 0;
1133    for (const auto & test : cases) {
1134        const sampler_comparison_output output = run_penalties_comparison(
1135                params, 64, test.repeat, test.frequency, test.presence, "Hello Hello world");
1136        GGML_ASSERT(output.expected.size() == output.actual.logits.size());
1137        const sampler_comparison_stats stats = compare_sampler_outputs(
1138                test.name, map_logits(output.expected), output.actual);
1139        n_failed += stats.n_mismatch != 0;
1140    }
1141
1142    GGML_ASSERT(n_failed == 0);
1143}
1144
1145static void compare_top_k_penalties_logits(
1146        const test_params & params,
1147        int32_t k,
1148        int32_t penalty_last_n,
1149        float penalty_repeat,
1150        float penalty_freq,
1151        float penalty_present,
1152        const std::string & prompt,
1153        penalties_position position) {
1154    const auto * vocab = llama_model_get_vocab(params.model.get());
1155    const std::vector<float> raw_logits = decode_raw_logits(params, prompt);
1156    const int n_vocab = (int) raw_logits.size();
1157
1158    GGML_ASSERT(n_vocab > k);
1159
1160    const sampler_init_fn init_top_k = [k]() {
1161        return llama_sampler_init_top_k(k);
1162    };
1163    llama_sampler_ptr top_k(init_top_k());
1164    const std::vector<llama_token_data> top_k_data = apply_cpu_sampler(raw_logits, top_k.get());
1165    GGML_ASSERT(top_k_data.size() == (size_t) k);
1166    const llama_token retained_history_token = top_k_data[0].id;
1167
1168    llama_token excluded_history_token = LLAMA_TOKEN_NULL;
1169    for (llama_token token = 0; token < n_vocab; ++token) {
1170        const auto it = std::find_if(top_k_data.begin(), top_k_data.end(), [token](const llama_token_data & data) {
1171            return data.id == token;
1172        });
1173        if (it == top_k_data.end()) {
1174            excluded_history_token = token;
1175            break;
1176        }
1177    }
1178    GGML_ASSERT(excluded_history_token != LLAMA_TOKEN_NULL);
1179
1180    const auto add_samplers = [&](llama_sampler * chain) {
1181        add_filter_and_penalties(chain, init_top_k, n_vocab,
1182                penalty_last_n, penalty_repeat, penalty_freq, penalty_present, position);
1183    };
1184
1185    auto accept_history = [&](llama_sampler * smpl) {
1186        accept_prompt(smpl, vocab, prompt);
1187        llama_sampler_accept(smpl, excluded_history_token);
1188        llama_sampler_accept(smpl, excluded_history_token);
1189        llama_sampler_accept(smpl, retained_history_token);
1190        llama_sampler_accept(smpl, retained_history_token);
1191    };
1192
1193    const sampler_comparison_output output = run_sampler_comparison(
1194            params, prompt, raw_logits, add_samplers, accept_history);
1195
1196    GGML_ASSERT(output.expected.size() == (size_t) k);
1197    GGML_ASSERT(output.actual.logits.size() == (size_t) k);
1198
1199    const std::unordered_map<llama_token, float> expected_logits = map_logits(output.expected);
1200
1201    if (position == penalties_position::after_filter) {
1202        GGML_ASSERT(expected_logits.find(retained_history_token) != expected_logits.end());
1203        GGML_ASSERT(fabsf(expected_logits.at(retained_history_token) - raw_logits[retained_history_token]) > 1e-6f);
1204        GGML_ASSERT(expected_logits.find(excluded_history_token) == expected_logits.end());
1205        GGML_ASSERT(std::find(output.actual.candidates.begin(), output.actual.candidates.end(),
1206                    excluded_history_token) == output.actual.candidates.end());
1207    } else {
1208        const std::unordered_map<llama_token, float> unpenalized_logits = map_logits(top_k_data);
1209        bool changed = false;
1210        for (const auto & item : expected_logits) {
1211            const auto it = unpenalized_logits.find(item.first);
1212            if (it == unpenalized_logits.end() || fabsf(it->second - item.second) > 1e-6f) {
1213                changed = true;
1214                break;
1215            }
1216        }
1217        GGML_ASSERT(changed);
1218    }
1219
1220    const char * name = position == penalties_position::before_filter
1221        ? "penalties top-k"
1222        : "top-k penalties";
1223    const sampler_comparison_stats stats = compare_sampler_outputs(
1224            name, expected_logits, output.actual);
1225    GGML_ASSERT(stats.n_masked == 0);
1226    GGML_ASSERT(stats.n_mismatch == 0);
1227}
1228
1229static void compare_masking_penalties_logits(
1230        const test_params & params,
1231        const char * filter_name,
1232        const sampler_init_fn & init_filter,
1233        int32_t penalty_last_n,
1234        float penalty_repeat,
1235        float penalty_freq,
1236        float penalty_present,
1237        const std::string & prompt,
1238        penalties_position position,
1239        bool allow_extra_candidates,
1240        bool add_history = true) {
1241    const auto * vocab = llama_model_get_vocab(params.model.get());
1242    const std::vector<float> raw_logits = decode_raw_logits(params, prompt);
1243    const int n_vocab = (int) raw_logits.size();
1244    llama_sampler_ptr filter(init_filter());
1245    const std::vector<llama_token_data> filtered_data = apply_cpu_sampler(raw_logits, filter.get());
1246    GGML_ASSERT(!filtered_data.empty());
1247    GGML_ASSERT(filtered_data.size() < (size_t) n_vocab);
1248
1249    const llama_token penalized_token = filtered_data[0].id;
1250    std::unordered_set<llama_token> retained_tokens;
1251    retained_tokens.reserve(filtered_data.size());
1252    for (const auto & data : filtered_data) {
1253        retained_tokens.insert(data.id);
1254    }
1255
1256    llama_token masked_token = LLAMA_TOKEN_NULL;
1257    for (llama_token token = 0; token < n_vocab; ++token) {
1258        if (retained_tokens.find(token) == retained_tokens.end()) {
1259            masked_token = token;
1260            break;
1261        }
1262    }
1263    GGML_ASSERT(masked_token != LLAMA_TOKEN_NULL);
1264
1265    const auto add_samplers = [&](llama_sampler * chain) {
1266        add_filter_and_penalties(chain, init_filter, n_vocab,
1267                penalty_last_n, penalty_repeat, penalty_freq, penalty_present, position);
1268    };
1269    auto accept_history = [&](llama_sampler * smpl) {
1270        if (!add_history) {
1271            return;
1272        }
1273        accept_prompt(smpl, vocab, prompt);
1274        llama_sampler_accept(smpl, penalized_token);
1275        llama_sampler_accept(smpl, penalized_token);
1276        llama_sampler_accept(smpl, masked_token);
1277        llama_sampler_accept(smpl, masked_token);
1278    };
1279
1280    const sampler_comparison_output output = run_sampler_comparison(
1281            params, prompt, raw_logits, add_samplers, accept_history);
1282
1283    GGML_ASSERT(output.actual.logits.size() == (size_t) n_vocab);
1284
1285    const std::unordered_map<llama_token, float> expected_logits = map_logits(output.expected);
1286
1287    GGML_ASSERT(expected_logits.find(masked_token) == expected_logits.end());
1288    if (add_history) {
1289        if (position == penalties_position::after_filter) {
1290            GGML_ASSERT(expected_logits.find(penalized_token) != expected_logits.end());
1291            GGML_ASSERT(fabsf(expected_logits.at(penalized_token) - raw_logits[penalized_token]) > 1e-6f);
1292        } else {
1293            llama_sampler_ptr penalties(llama_sampler_init_penalties(
1294                        n_vocab, penalty_last_n, penalty_repeat, penalty_freq, penalty_present));
1295            accept_history(penalties.get());
1296            const std::unordered_map<llama_token, float> penalized_logits =
1297                map_logits(apply_cpu_sampler(raw_logits, penalties.get()));
1298            GGML_ASSERT(fabsf(penalized_logits.at(penalized_token) - raw_logits[penalized_token]) > 1e-6f);
1299        }
1300    }
1301
1302    const std::string name = position == penalties_position::before_filter
1303        ? "penalties " + std::string(filter_name)
1304        : std::string(filter_name) + " penalties";
1305    const sampler_comparison_stats stats = compare_sampler_outputs(
1306            name.c_str(), expected_logits, output.actual, allow_extra_candidates);
1307    const float masked_logit = find_backend_logit(output.actual, masked_token);
1308    GGML_ASSERT(stats.n_masked > 0);
1309    GGML_ASSERT(std::isinf(masked_logit) && masked_logit < 0.0f);
1310    GGML_ASSERT(stats.n_mismatch == 0);
1311}
1312
1313static void test_backend_penalties_sampling(const test_params & params) {
1314    printf("Testing backend penalties (repeat + freq + presence)\n");
1315    compare_penalties_logits(params, 64, 1.1f, 0.5f, 0.25f, "Hello Hello world");
1316
1317    printf("Testing backend penalties with penalty_last_n > 64\n");
1318    const auto * vocab = llama_model_get_vocab(params.model.get());
1319    std::vector<llama_token> tokens(8);
1320    int32_t n_tok = llama_tokenize(vocab, "a", 1, tokens.data(), (int32_t) tokens.size(), false, false);
1321    if (n_tok < 0) {
1322        tokens.resize(-n_tok);
1323        n_tok = llama_tokenize(vocab, "a", 1, tokens.data(), (int32_t) tokens.size(), false, false);
1324    }
1325    GGML_ASSERT(n_tok > 0);
1326    const llama_token tok = tokens[0];
1327
1328    compare_penalties_logits(params, 80, 1.15f, 0.1f, 0.05f, "a", [tok](llama_sampler * smpl) {
1329        // accept_prompt already accepted BOS + one 'a'; fill the ring to n=80
1330        for (int i = 0; i < 78; ++i) {
1331            llama_sampler_accept(smpl, tok);
1332        }
1333    });
1334
1335    printf("Testing backend penalties without filler entries\n");
1336    compare_penalties_logits(params, 64, 1.1f, 0.5f, 0.25f, "Hello", [](llama_sampler * smpl) {
1337        for (llama_token token = 0; token < 64; ++token) {
1338            llama_sampler_accept(smpl, token);
1339        }
1340    });
1341
1342    printf("Testing backend top-k followed by penalties\n");
1343    compare_top_k_penalties_logits(params, 8, 64, 1.1f, 0.5f, 0.25f, "Hello",
1344            penalties_position::after_filter);
1345
1346    printf("Testing backend penalties followed by top-k\n");
1347    compare_top_k_penalties_logits(params, 8, 64, 1.1f, 0.5f, 0.25f, "Hello",
1348            penalties_position::before_filter);
1349
1350    printf("Testing backend top-p followed by penalties\n");
1351    compare_masking_penalties_logits(params, "top-p", []() {
1352        return llama_sampler_init_top_p(0.9f, 0);
1353    }, 64, 1.1f, 0.5f, 0.25f, "Hello", penalties_position::after_filter, true);
1354
1355    printf("Testing backend top-p followed by penalties with a large history window\n");
1356    compare_masking_penalties_logits(params, "top-p large-window", []() {
1357        return llama_sampler_init_top_p(0.9f, 0);
1358    }, 4096, 1.1f, 0.5f, 0.25f, "Hello", penalties_position::after_filter, true);
1359
1360    printf("Testing backend penalties followed by top-p\n");
1361    compare_masking_penalties_logits(params, "top-p", []() {
1362        return llama_sampler_init_top_p(0.9f, 0);
1363    }, 64, 1.1f, 0.5f, 0.25f, "Hello", penalties_position::before_filter, true);
1364
1365    printf("Testing backend min-p followed by penalties\n");
1366    compare_masking_penalties_logits(params, "min-p", []() {
1367        return llama_sampler_init_min_p(0.1f, 0);
1368    }, 64, 1.1f, 0.5f, 0.25f, "Hello", penalties_position::after_filter, false);
1369
1370    printf("Testing backend penalties followed by min-p\n");
1371    compare_masking_penalties_logits(params, "min-p", []() {
1372        return llama_sampler_init_min_p(0.1f, 0);
1373    }, 64, 1.1f, 0.5f, 0.25f, "Hello", penalties_position::before_filter, false);
1374
1375    printf("Testing backend top-p followed by penalties with empty history\n");
1376    compare_masking_penalties_logits(params, "top-p empty", []() {
1377        return llama_sampler_init_top_p(0.9f, 0);
1378    }, 64, 1.1f, 0.5f, 0.25f, "Hello", penalties_position::after_filter, true, false);
1379
1380    printf("Testing backend top-p followed by individual penalties\n");
1381    compare_masking_penalties_logits(params, "top-p repeat", []() {
1382        return llama_sampler_init_top_p(0.9f, 0);
1383    }, 64, 1.1f, 0.0f, 0.0f, "Hello", penalties_position::after_filter, true);
1384    compare_masking_penalties_logits(params, "top-p frequency", []() {
1385        return llama_sampler_init_top_p(0.9f, 0);
1386    }, 64, 1.0f, 0.5f, 0.0f, "Hello", penalties_position::after_filter, true);
1387    compare_masking_penalties_logits(params, "top-p presence", []() {
1388        return llama_sampler_init_top_p(0.9f, 0);
1389    }, 64, 1.0f, 0.0f, 0.25f, "Hello", penalties_position::after_filter, true);
1390
1391    printf("Testing backend penalty parameter values\n");
1392    test_penalty_parameter_values(params);
1393
1394    printf("backend penalties sampling test PASSED\n");
1395}
1396
1397// This test verifies that it is possible to have two different backend samplers,
1398// one that uses the backend dist sampler, and another that uses CPU dist sampler.
1399static void test_backend_mixed_sampling(const test_params & params) {
1400    struct llama_sampler_chain_params chain_params_0 = llama_sampler_chain_default_params();
1401    llama_sampler_ptr sampler_chain_0(llama_sampler_chain_init(chain_params_0));
1402    llama_sampler_chain_add(sampler_chain_0.get(), llama_sampler_init_dist(88));
1403
1404    int k = 40;
1405    struct llama_sampler_chain_params chain_params_1 = llama_sampler_chain_default_params();
1406    llama_sampler_ptr sampler_chain_1(llama_sampler_chain_init(chain_params_1));
1407    llama_sampler_chain_add(sampler_chain_1.get(), llama_sampler_init_top_k(k));
1408
1409    std::vector<llama_sampler_seq_config> backend_sampler_configs = {
1410        { 0, sampler_chain_0.get() },
1411        { 1, sampler_chain_1.get() }
1412    };
1413
1414    test_context test_ctx(params, backend_sampler_configs);
1415
1416    std::map<llama_seq_id, std::string> prompts = {
1417        {0, "Hello"},
1418        {1, "Some"}
1419    };
1420
1421    if (!test_ctx.decode(prompts)) {
1422        GGML_ASSERT(false && "Failed to decode token");
1423    }
1424
1425    // Verify sequence 0 that used the dist backend sampler.
1426    {
1427        int32_t batch_idx = test_ctx.idx_for_seq(0);
1428        llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
1429        const std::string token_str = test_ctx.token_to_piece(token, false);
1430        printf("sampled token id=%d, string='%s'\n", token, token_str.c_str());
1431        GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
1432        //GGML_ASSERT(llama_get_sampled_logits_ith(test_ctx.ctx.get(), batch_idx) == nullptr);
1433        //GGML_ASSERT(llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx) == 0);
1434    }
1435
1436    // Verify sequence 1 that used the top-k backend sampler.
1437    {
1438        int32_t batch_idx = test_ctx.idx_for_seq(1);
1439        float * logits = llama_get_sampled_logits_ith(test_ctx.ctx.get(), batch_idx);
1440        GGML_ASSERT(logits != nullptr);
1441        size_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), batch_idx);
1442        GGML_ASSERT(n_logits == (size_t) k);
1443        GGML_ASSERT(llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx) == LLAMA_TOKEN_NULL);
1444    }
1445
1446    printf("backend mixed sampling test PASSED\n");
1447}
1448
1449static void test_backend_set_sampler(const test_params & params) {
1450    const int seq_id = 0;
1451    const int32_t seed = 88;
1452
1453    struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params();
1454    llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params));
1455    llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed));
1456    std::vector<llama_sampler_seq_config> backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }};
1457
1458    test_context test_ctx(params, backend_sampler_configs);
1459
1460    if (!test_ctx.decode({{seq_id, "Hello"}})) {
1461        GGML_ASSERT(false && "Failed to decode token");
1462    }
1463
1464    int32_t batch_idx = test_ctx.idx_for_seq(seq_id);
1465
1466    // Sample using backend sampler configured above
1467    llama_token backend_token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
1468    const std::string backend_token_str = test_ctx.token_to_piece(backend_token, false);
1469    printf("dist sampled token = %d, string='%s'\n", backend_token, backend_token_str.c_str());
1470
1471    // Now clear the backend sampler for this sequence.
1472    llama_set_sampler(test_ctx.ctx.get(), seq_id, nullptr);
1473    printf("Cleared backend sampler for seq_id %d\n", seq_id);
1474
1475    // Sample using CPU sampler
1476    struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
1477    llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
1478    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(18));
1479
1480    std::map<llama_seq_id, llama_token> tokens = { { seq_id, backend_token}, };
1481    if (!test_ctx.decode_tokens(tokens)) {
1482        GGML_ASSERT(false && "Failed to decode token");
1483    }
1484
1485    // Should not have any sampled token or probs after clearing the backend sampler.
1486    const int32_t idx = test_ctx.idx_for_seq(seq_id);
1487    GGML_ASSERT(llama_get_sampled_token_ith(test_ctx.ctx.get(), idx) == LLAMA_TOKEN_NULL);
1488    GGML_ASSERT(llama_get_sampled_probs_ith(test_ctx.ctx.get(), idx) == nullptr);
1489
1490    // Sample the token using the CPU sampler chain.
1491    llama_token token2 = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), seq_id);
1492    const std::string token2_str = test_ctx.token_to_piece(token2, false);
1493    printf("CPU sampled token after clearing backend sampler: id=%d, string='%s'\n", token2, token2_str.c_str());
1494    std::map<llama_seq_id, llama_token> tokens2 = { { seq_id, token2}, };
1495
1496    // Set a new backend sampler for the sequence.
1497    struct llama_sampler_chain_params new_backend_chain_params = llama_sampler_chain_default_params();
1498    llama_sampler_ptr new_backend_sampler_chain(llama_sampler_chain_init(new_backend_chain_params));
1499    llama_sampler_chain_add(new_backend_sampler_chain.get(), llama_sampler_init_top_k(20));
1500    llama_sampler_chain_add(new_backend_sampler_chain.get(), llama_sampler_init_dist(seed));
1501    llama_set_sampler(test_ctx.ctx.get(), seq_id, new_backend_sampler_chain.get());
1502
1503    if (!test_ctx.decode_tokens(tokens2)) {
1504        GGML_ASSERT(false && "Failed to decode token");
1505    }
1506
1507    llama_token new_backend_token = llama_get_sampled_token_ith(test_ctx.ctx.get(), test_ctx.idx_for_seq(seq_id));
1508    const std::string new_backend_token_str = test_ctx.token_to_piece(new_backend_token, false);
1509    printf("dist sampled token = %d, string='%s'\n", new_backend_token, new_backend_token_str.c_str());
1510
1511    printf("backend set sampler test PASSED\n");
1512}
1513
1514static void test_backend_cpu_mixed_batch(const test_params & params) {
1515    // Sequence 0 uses backend sampling
1516    struct llama_sampler_chain_params chain_params_0 = llama_sampler_chain_default_params();
1517    llama_sampler_ptr sampler_chain_0(llama_sampler_chain_init(chain_params_0));
1518    llama_sampler_chain_add(sampler_chain_0.get(), llama_sampler_init_dist(88));
1519
1520    std::vector<llama_sampler_seq_config> backend_sampler_configs = {
1521        { 0, sampler_chain_0.get() },
1522    };
1523
1524    // We need 2 sequences: seq 0 with backend sampling, seq 1 with CPU sampling
1525    test_context test_ctx(params, backend_sampler_configs, 2);
1526
1527    std::map<llama_seq_id, std::string> prompts = {
1528        {0, "Hello"}, // Will use backend sampling
1529        {1, "Some"}   // Will use CPU sampling
1530    };
1531
1532    if (!test_ctx.decode(prompts)) {
1533        GGML_ASSERT(false && "Failed to decode token");
1534    }
1535
1536    // Verify sequence 0 (backend sampled)
1537    {
1538        int32_t batch_idx = test_ctx.idx_for_seq(0);
1539        llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
1540        const std::string token_str = test_ctx.token_to_piece(token, false);
1541        printf("Seq 0 (backend) sampled token id=%d, string='%s'\n", token, token_str.c_str());
1542        GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
1543    }
1544
1545    // Verify sequence 1 (CPU sampled)
1546    {
1547        int32_t batch_idx = test_ctx.idx_for_seq(1);
1548
1549        llama_token backend_token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
1550        GGML_ASSERT(backend_token == LLAMA_TOKEN_NULL);
1551
1552        struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
1553        llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
1554        llama_sampler_chain_add(chain.get(), llama_sampler_init_greedy());
1555
1556        llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
1557        const std::string token_str = test_ctx.token_to_piece(token, false);
1558        printf("Seq 1 (CPU) sampled token id=%d, string='%s'\n", token, token_str.c_str());
1559        GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
1560    }
1561
1562    // Clear/remove the backend sampler, and sample again
1563    {
1564        // clear the backend sampler for seq 0 so that there are no backend
1565        // samplers.
1566        llama_set_sampler(test_ctx.ctx.get(), 0, nullptr);
1567
1568        // Create a CPU sampler and verify we can sample from it.
1569        struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
1570        llama_sampler_ptr chain(llama_sampler_chain_init(chain_params));
1571        llama_sampler_chain_add(chain.get(), llama_sampler_init_greedy());
1572
1573        int32_t batch_idx = test_ctx.idx_for_seq(1);
1574        llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx);
1575        if (!test_ctx.decode_token(token, 1)) {
1576            GGML_ASSERT(false && "Failed to decode token");
1577        }
1578    }
1579
1580    // Set a backend sampler so that we can verify that it can be reset
1581    {
1582        struct llama_sampler_chain_params chain_params = llama_sampler_chain_default_params();
1583        llama_sampler_ptr sampler_chain(llama_sampler_chain_init(chain_params));
1584        llama_sampler_chain_add(sampler_chain.get(), llama_sampler_init_dist(88));
1585
1586        llama_set_sampler(test_ctx.ctx.get(), 0, sampler_chain.get());
1587
1588        if (!test_ctx.decode_token(3834, 0)) {
1589            GGML_ASSERT(false && "Failed to decode token");
1590        }
1591
1592        int32_t batch_idx = test_ctx.idx_for_seq(0);
1593        llama_token token = llama_get_sampled_token_ith(test_ctx.ctx.get(), batch_idx);
1594        const std::string token_str = test_ctx.token_to_piece(token, false);
1595        printf("re-added backend sampled token id=%d, string='%s'\n", token, token_str.c_str());
1596        GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab);
1597    }
1598
1599    printf("backend-cpu mixed batch test PASSED\n");
1600}
1601
1602static void test_backend_multi_output_limit(const test_params & params) {
1603    const llama_seq_id seq_id = 0;
1604
1605    llama_sampler_ptr chain(llama_sampler_chain_init(llama_sampler_chain_default_params()));
1606    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(88));
1607    std::vector<llama_sampler_seq_config> configs = {{ seq_id, chain.get() }};
1608    test_context test_ctx(params, configs, 1, 3, 0, 2);
1609
1610    llama_batch batch = llama_batch_init(3, 0, 1);
1611    for (int i = 0; i < 3; ++i) {
1612        common_batch_add(batch, llama_vocab_bos(test_ctx.vocab), i, { seq_id }, true);
1613    }
1614
1615    printf(">>> test_backend_multi_output_limit expected error start:\n");
1616    const int ret = llama_decode(test_ctx.ctx.get(), batch);
1617    GGML_ASSERT(ret != 0 && "llama_decode should reject outputs above the per-sequence limit");
1618    printf("<<< test_backend_multi_output_limit expected error end.\n");
1619
1620    llama_batch_free(batch);
1621
1622    printf("backend multi-output limit test PASSED\n");
1623}
1624
1625static void test_backend_multi_sequence_multi_output_dist(const test_params & params) {
1626    const llama_vocab * vocab = llama_model_get_vocab(params.model.get());
1627    const int32_t n_vocab = llama_vocab_n_tokens(vocab);
1628    const uint32_t seeds[] = { 88, 1337 };
1629    // reduce the chance that swapped random inputs select the same token
1630    const float temp = 10.0f;
1631
1632    llama_sampler_ptr chain_0(llama_sampler_chain_init(llama_sampler_chain_default_params()));
1633    llama_sampler_ptr chain_1(llama_sampler_chain_init(llama_sampler_chain_default_params()));
1634    llama_sampler_chain_add(chain_0.get(), llama_sampler_init_temp(temp));
1635    llama_sampler_chain_add(chain_0.get(), llama_sampler_init_dist(seeds[0]));
1636    llama_sampler_chain_add(chain_1.get(), llama_sampler_init_temp(temp));
1637    llama_sampler_chain_add(chain_1.get(), llama_sampler_init_dist(seeds[1]));
1638    std::vector<llama_sampler_seq_config> configs = {
1639        { 0, chain_0.get() },
1640        { 1, chain_1.get() },
1641    };
1642    test_context test_ctx(params, configs, 2, 4, 0, 2);
1643
1644    std::vector<llama_sampler_seq_config> reference_configs;
1645    test_context reference_ctx(params, reference_configs, 2, 4);
1646
1647    const llama_token seq_tokens[2][2] = {
1648        { llama_vocab_bos(vocab), llama_vocab_eos(vocab) },
1649        { llama_vocab_eos(vocab), llama_vocab_bos(vocab) },
1650    };
1651
1652    llama_batch batch = llama_batch_init(4, 0, 1);
1653    for (int pos = 0; pos < 2; ++pos) {
1654        common_batch_add(batch, seq_tokens[0][pos], pos, { 0 }, true);
1655        common_batch_add(batch, seq_tokens[1][pos], pos, { 1 }, true);
1656    }
1657
1658    GGML_ASSERT(llama_decode(test_ctx.ctx.get(), batch) == 0);
1659    GGML_ASSERT(llama_decode(reference_ctx.ctx.get(), batch) == 0);
1660
1661    std::mt19937 reference_rngs[] = {
1662        std::mt19937(seeds[0]),
1663        std::mt19937(seeds[1]),
1664    };
1665    std::uniform_real_distribution<double> reference_dist(0.0, 1.0);
1666
1667    for (int i = 0; i < batch.n_tokens; ++i) {
1668        const llama_seq_id seq_id = batch.seq_id[i][0];
1669        GGML_ASSERT(seq_id == 0 || seq_id == 1);
1670
1671        llama_sampler * chain = seq_id == 0 ? chain_0.get() : chain_1.get();
1672        const llama_token backend_token = llama_sampler_sample(chain, test_ctx.ctx.get(), i);
1673        const float * sampled_logits = llama_get_sampled_logits_ith(test_ctx.ctx.get(), i);
1674        const float * sampled_probs = llama_get_sampled_probs_ith(test_ctx.ctx.get(), i);
1675        const uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), i);
1676        const uint32_t n_probs = llama_get_sampled_probs_count_ith(test_ctx.ctx.get(), i);
1677        const float * reference_logits = llama_get_logits_ith(reference_ctx.ctx.get(), i);
1678
1679        GGML_ASSERT(backend_token >= 0 && backend_token < n_vocab);
1680        GGML_ASSERT(sampled_logits != nullptr);
1681        GGML_ASSERT(sampled_probs != nullptr);
1682        GGML_ASSERT(reference_logits != nullptr);
1683        GGML_ASSERT(n_logits == (uint32_t) n_vocab);
1684        GGML_ASSERT(n_probs == (uint32_t) n_vocab);
1685
1686        float prob_sum = 0.0f;
1687        float cumsum_before = 0.0f;
1688        for (llama_token token = 0; token < n_vocab; ++token) {
1689            const float expected_logit = reference_logits[token] / temp;
1690            const float tolerance = 1e-4f * std::max(1.0f, std::fabs(expected_logit));
1691            GGML_ASSERT(std::fabs(sampled_logits[token] - expected_logit) <= tolerance);
1692            GGML_ASSERT(std::isfinite(sampled_probs[token]));
1693            GGML_ASSERT(sampled_probs[token] >= 0.0f);
1694
1695            prob_sum += sampled_probs[token];
1696            if (token < backend_token) {
1697                cumsum_before += sampled_probs[token];
1698            }
1699        }
1700
1701        GGML_ASSERT(std::fabs(prob_sum - 1.0f) <= 1e-3f);
1702
1703        const float rnd = reference_dist(reference_rngs[seq_id]);
1704        const float cumsum_sampled = cumsum_before + sampled_probs[backend_token];
1705        GGML_ASSERT(rnd >= cumsum_before - 1e-4f);
1706        GGML_ASSERT(rnd <= cumsum_sampled + 1e-4f);
1707    }
1708
1709    llama_batch_free(batch);
1710
1711    printf("backend multi-sequence multi-output dist test PASSED\n");
1712}
1713
1714static void test_backend_multi_output_dist_transaction(const test_params & params) {
1715    const llama_seq_id seq_id = 0;
1716    const uint32_t seed = 95;
1717    const llama_vocab * vocab = llama_model_get_vocab(params.model.get());
1718
1719    llama_sampler_ptr chain(llama_sampler_chain_init(llama_sampler_chain_default_params()));
1720    llama_sampler_chain_add(chain.get(), llama_sampler_init_temp(10.0f));
1721    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(seed));
1722    std::vector<llama_sampler_seq_config> configs = {{ seq_id, chain.get() }};
1723    test_context test_ctx(params, configs, 1, 3, 2, 3);
1724
1725    auto verify_random = [&](int32_t row, float rnd, bool accept = true) {
1726        const llama_token token = accept ?
1727            llama_sampler_sample(chain.get(), test_ctx.ctx.get(), row) :
1728            llama_get_sampled_token_ith(test_ctx.ctx.get(), row);
1729        const float * probs = llama_get_sampled_probs_ith(test_ctx.ctx.get(), row);
1730
1731        GGML_ASSERT(token >= 0 && token < llama_vocab_n_tokens(vocab));
1732        GGML_ASSERT(probs != nullptr);
1733
1734        float cumsum_before = 0.0f;
1735        for (llama_token i = 0; i < token; ++i) {
1736            cumsum_before += probs[i];
1737        }
1738
1739        const float cumsum_sampled = cumsum_before + probs[token];
1740        GGML_ASSERT(rnd >= cumsum_before - 1e-4f);
1741        GGML_ASSERT(rnd <= cumsum_sampled + 1e-4f);
1742    };
1743
1744    std::mt19937 rng(seed);
1745    std::uniform_real_distribution<double> dist(0.0, 1.0);
1746    float randoms[3];
1747    for (float & rnd : randoms) {
1748        rnd = dist(rng);
1749    }
1750
1751    int32_t pos = 0;
1752    auto decode = [&]() {
1753        llama_batch batch = llama_batch_init(3, 0, 1);
1754        for (int32_t i = 0; i < 3; ++i) {
1755            common_batch_add(batch, llama_vocab_bos(vocab), pos++, { seq_id }, true);
1756        }
1757        GGML_ASSERT(llama_decode(test_ctx.ctx.get(), batch) == 0);
1758        return batch;
1759    };
1760
1761    llama_batch batch = decode();
1762    verify_random(0, randoms[0], false);
1763    llama_batch_free(batch);
1764
1765    batch = decode();
1766    verify_random(0, randoms[0]);
1767    verify_random(1, randoms[1]);
1768    llama_batch_free(batch);
1769
1770    batch = decode();
1771    llama_sampler_ptr saved(llama_sampler_clone(chain.get()));
1772    verify_random(0, randoms[2]);
1773    llama_batch_free(batch);
1774
1775    llama_sampler_copy(saved.get(), chain.get());
1776
1777    batch = decode();
1778    verify_random(0, randoms[2]);
1779    llama_batch_free(batch);
1780
1781    printf("backend multi-output dist transaction test PASSED\n");
1782}
1783
1784static void test_backend_multi_output_sampling_chain(const test_params & params) {
1785    const llama_seq_id seq_id = 0;
1786    const uint32_t seed = 88;
1787    const float p = 0.9f;
1788    const float temp = 0.8f;
1789    const float cdf_epsilon = 1e-4f;
1790    const llama_vocab * vocab = llama_model_get_vocab(params.model.get());
1791    const int32_t n_vocab = llama_vocab_n_tokens(vocab);
1792    const uint32_t k = std::min<uint32_t>(512, n_vocab);
1793    const llama_logit_bias bias = { llama_vocab_bos(vocab), -0.1f };
1794
1795    auto make_filter_chain = [&]() {
1796        llama_sampler_ptr result(llama_sampler_chain_init(llama_sampler_chain_default_params()));
1797        llama_sampler_chain_add(result.get(), llama_sampler_init_logit_bias(n_vocab, 1, &bias));
1798        llama_sampler_chain_add(result.get(), llama_sampler_init_top_k(k));
1799        llama_sampler_chain_add(result.get(), llama_sampler_init_top_p(p, 1));
1800        llama_sampler_chain_add(result.get(), llama_sampler_init_min_p(0.01f, 1));
1801        llama_sampler_chain_add(result.get(), llama_sampler_init_temp(temp));
1802        return result;
1803    };
1804
1805    llama_sampler_ptr chain = make_filter_chain();
1806    llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(seed));
1807    std::vector<llama_sampler_seq_config> configs = {{ seq_id, chain.get() }};
1808    test_context test_ctx(params, configs, 1, 2, 2, 2);
1809
1810    std::vector<llama_sampler_seq_config> reference_configs;
1811    test_context reference_ctx(params, reference_configs, 1, 2, 2);
1812
1813    llama_sampler_ptr reference_bias(llama_sampler_init_logit_bias(n_vocab, 1, &bias));
1814    llama_sampler_ptr reference_top_k(llama_sampler_init_top_k(k));
1815    llama_sampler_ptr reference_top_p(llama_sampler_init_top_p(p, 1));
1816    llama_sampler_ptr reference_min_p(llama_sampler_init_min_p(0.01f, 1));
1817    llama_sampler_ptr reference_temp(llama_sampler_init_temp(temp));
1818    std::vector<llama_token_data> reference_data(n_vocab);
1819
1820    auto make_batch = [&](int32_t pos) {
1821        llama_batch batch = llama_batch_init(2, 0, 1);
1822        for (int i = 0; i < 2; ++i) {
1823            common_batch_add(batch, llama_vocab_bos(vocab), pos + i, { seq_id }, true);
1824        }
1825        return batch;
1826    };
1827
1828    llama_batch batch = make_batch(0);
1829    GGML_ASSERT(llama_decode(test_ctx.ctx.get(), batch) == 0);
1830    GGML_ASSERT(llama_decode(reference_ctx.ctx.get(), batch) == 0);
1831
1832    for (int i = 0; i < batch.n_tokens; ++i) {
1833        const llama_token backend_token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), i);
1834        const float * sampled_logits = llama_get_sampled_logits_ith(test_ctx.ctx.get(), i);
1835        const float * sampled_probs = llama_get_sampled_probs_ith(test_ctx.ctx.get(), i);
1836        const llama_token * sampled_candidates = llama_get_sampled_candidates_ith(test_ctx.ctx.get(), i);
1837        const uint32_t n_logits = llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), i);
1838        const uint32_t n_probs = llama_get_sampled_probs_count_ith(test_ctx.ctx.get(), i);
1839        const uint32_t n_candidates = llama_get_sampled_candidates_count_ith(test_ctx.ctx.get(), i);
1840        const float * reference_logits = llama_get_logits_ith(reference_ctx.ctx.get(), i);
1841
1842        GGML_ASSERT(backend_token >= 0 && backend_token < n_vocab);
1843        GGML_ASSERT(sampled_logits != nullptr);
1844        GGML_ASSERT(sampled_probs != nullptr);
1845        GGML_ASSERT(sampled_candidates != nullptr);
1846        GGML_ASSERT(reference_logits != nullptr);
1847        GGML_ASSERT(n_logits == k);
1848        GGML_ASSERT(n_probs == n_logits);
1849        GGML_ASSERT(n_candidates == n_logits);
1850
1851        for (llama_token token = 0; token < n_vocab; ++token) {
1852            reference_data[token] = { token, reference_logits[token], 0.0f };
1853        }
1854
1855        llama_token_data_array reference = {
1856            /* .data     = */ reference_data.data(),
1857            /* .size     = */ reference_data.size(),
1858            /* .selected = */ LLAMA_TOKEN_NULL,
1859            /* .sorted   = */ false,
1860        };
1861
1862        llama_sampler_apply(reference_bias.get(), &reference);
1863        llama_sampler_apply(reference_top_k.get(), &reference);
1864        llama_sampler_apply(reference_top_p.get(), &reference);
1865        GGML_ASSERT(reference.size > 0);
1866
1867        float cdf = 0.0f;
1868        for (size_t j = 0; j < reference.size; ++j) {
1869            cdf += reference.data[j].p;
1870        }
1871        const float cdf_before = cdf - reference.data[reference.size - 1].p;
1872        const float boundary_distance = std::min(std::fabs(cdf_before - p), std::fabs(cdf - p));
1873
1874        llama_sampler_apply(reference_min_p.get(), &reference);
1875        llama_sampler_apply(reference_temp.get(), &reference);
1876
1877        std::unordered_map<llama_token, float> reference_by_id;
1878        for (size_t j = 0; j < reference.size; ++j) {
1879            reference_by_id.emplace(reference.data[j].id, reference.data[j].logit);
1880        }
1881        size_t n_backend_only = 0;
1882        int32_t sampled_index = -1;
1883        float prob_sum = 0.0f;
1884
1885        for (uint32_t j = 0; j < n_logits; ++j) {
1886            GGML_ASSERT(sampled_candidates[j] >= 0 && sampled_candidates[j] < n_vocab);
1887            GGML_ASSERT(std::isfinite(sampled_probs[j]));
1888            GGML_ASSERT(sampled_probs[j] >= 0.0f);
1889            prob_sum += sampled_probs[j];
1890
1891            if (sampled_candidates[j] == backend_token) {
1892                sampled_index = j;
1893            }
1894            if (!std::isfinite(sampled_logits[j])) {
1895                GGML_ASSERT(std::isinf(sampled_logits[j]) && sampled_logits[j] < 0.0f);
1896                GGML_ASSERT(sampled_probs[j] == 0.0f);
1897                continue;
1898            }
1899
1900            const auto match = reference_by_id.find(sampled_candidates[j]);
1901            if (match == reference_by_id.end()) {
1902                ++n_backend_only;
1903                continue;
1904            }
1905
1906            const float tolerance = 1e-4f * std::max(1.0f, std::fabs(match->second));
1907            GGML_ASSERT(std::fabs(sampled_logits[j] - match->second) <= tolerance);
1908            reference_by_id.erase(match);
1909        }
1910
1911        const size_t n_reference_only = reference_by_id.size();
1912
1913        if (n_backend_only != 0 || n_reference_only != 0) {
1914            GGML_ASSERT(n_backend_only <= 1);
1915            GGML_ASSERT(n_reference_only <= 1);
1916            GGML_ASSERT(boundary_distance <= cdf_epsilon);
1917        }
1918
1919        GGML_ASSERT(sampled_index >= 0);
1920        GGML_ASSERT(std::isfinite(sampled_logits[sampled_index]));
1921        GGML_ASSERT(sampled_probs[sampled_index] > 0.0f);
1922        GGML_ASSERT(std::fabs(prob_sum - 1.0f) <= 1e-3f);
1923    }
1924
1925    llama_batch_free(batch);
1926
1927    batch = make_batch(2);
1928    GGML_ASSERT(llama_decode(test_ctx.ctx.get(), batch) == 0);
1929    llama_batch_free(batch);
1930
1931    printf("backend multi-output sampling chain test PASSED\n");
1932}
1933
1934static void test_backend_multi_output_cpu_suffix(const test_params & params) {
1935    const llama_seq_id seq_id = 0;
1936    const int32_t k = 8;
1937    const llama_vocab * vocab = llama_model_get_vocab(params.model.get());
1938
1939    auto make_chain = [&](test_single_output_backend_sampler ** sampler_ctx) {
1940        llama_sampler_ptr result(llama_sampler_chain_init(llama_sampler_chain_default_params()));
1941        llama_sampler_chain_add(result.get(), llama_sampler_init_top_k(k));
1942        llama_sampler_chain_add(result.get(), test_single_output_backend_sampler_init(sampler_ctx));
1943        llama_sampler_chain_add(result.get(), llama_sampler_init_dist(88));
1944        return result;
1945    };
1946
1947    {
1948        test_single_output_backend_sampler * sampler_ctx = nullptr;
1949        llama_sampler_ptr chain = make_chain(&sampler_ctx);
1950        std::vector<llama_sampler_seq_config> configs = {{ seq_id, chain.get() }};
1951        test_context test_ctx(params, configs, 1, 1, 0, 4);
1952
1953        llama_batch batch = llama_batch_init(1, 0, 1);
1954        common_batch_add(batch, llama_vocab_bos(vocab), 0, { seq_id }, true);
1955        GGML_ASSERT(llama_decode(test_ctx.ctx.get(), batch) == 0);
1956
1957        GGML_ASSERT(sampler_ctx->backend_initialized);
1958        GGML_ASSERT(sampler_ctx->backend_outputs_max_per_seq == 1);
1959        GGML_ASSERT(sampler_ctx->backend_apply_count > 0);
1960        GGML_ASSERT(sampler_ctx->apply_count == 0);
1961        GGML_ASSERT(llama_get_sampled_token_ith(test_ctx.ctx.get(), 0) != LLAMA_TOKEN_NULL);
1962
1963        llama_batch_free(batch);
1964    }
1965
1966    {
1967        test_single_output_backend_sampler * sampler_ctx = nullptr;
1968        llama_sampler_ptr chain = make_chain(&sampler_ctx);
1969        std::vector<llama_sampler_seq_config> configs = {{ seq_id, chain.get() }};
1970        test_context test_ctx(params, configs, 1, 2, 0, 0);
1971
1972        llama_batch batch = llama_batch_init(2, 0, 1);
1973        for (int i = 0; i < 2; ++i) {
1974            common_batch_add(batch, llama_vocab_bos(vocab), i, { seq_id }, true);
1975        }
1976        GGML_ASSERT(llama_decode(test_ctx.ctx.get(), batch) == 0);
1977
1978        GGML_ASSERT(!sampler_ctx->backend_initialized);
1979        GGML_ASSERT(sampler_ctx->backend_outputs_max_per_seq == 2);
1980        GGML_ASSERT(sampler_ctx->backend_apply_count == 0);
1981        for (int i = 0; i < batch.n_tokens; ++i) {
1982            GGML_ASSERT(llama_get_sampled_token_ith(test_ctx.ctx.get(), i) == LLAMA_TOKEN_NULL);
1983            GGML_ASSERT(llama_get_sampled_logits_count_ith(test_ctx.ctx.get(), i) == (uint32_t) k);
1984            GGML_ASSERT(llama_get_sampled_candidates_count_ith(test_ctx.ctx.get(), i) == (uint32_t) k);
1985            const llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), i);
1986            GGML_ASSERT(token >= 0 && token < llama_vocab_n_tokens(vocab));
1987        }
1988        GGML_ASSERT(sampler_ctx->apply_count == batch.n_tokens);
1989
1990        llama_batch_free(batch);
1991    }
1992
1993    printf("backend multi-output CPU suffix test PASSED\n");
1994}
1995
1996struct backend_test_case {
1997    std::string name;
1998    void (*fn)(const test_params &);
1999    bool enabled_by_default;
2000};
2001
2002static const backend_test_case BACKEND_TESTS[] = {
2003    { "greedy",          test_backend_greedy_sampling,         true  },
2004    { "logit_bias",      test_backend_logit_bias_sampling,     true  },
2005    { "penalties",       test_backend_penalties_sampling,      true  },
2006    { "temp",            test_backend_temp_sampling,           true  },
2007    { "temp_ext",        test_backend_temp_ext_sampling,       true  },
2008    { "top_k",           test_backend_top_k_sampling,          true  },
2009    { "multi_sequence",  test_backend_multi_sequence_sampling, true  },
2010    { "dist",            test_backend_dist_sampling,           true  },
2011    { "dist_and_cpu",    test_backend_dist_sampling_and_cpu,   true  },
2012    { "set_sampler",     test_backend_set_sampler,             true  },
2013    { "multi_output_limit",    test_backend_multi_output_limit,      true },
2014    { "multi_sequence_multi_output_dist", test_backend_multi_sequence_multi_output_dist, true },
2015    { "multi_output_dist_transaction", test_backend_multi_output_dist_transaction, true },
2016    { "multi_output_sampling_chain", test_backend_multi_output_sampling_chain, true },
2017    { "multi_output_cpu",      test_backend_multi_output_cpu_suffix, true },
2018    { "mixed",           test_backend_mixed_sampling,          true  },
2019    { "min_p",           test_backend_min_p_sampling,          true  },
2020    { "cpu_mixed",       test_backend_cpu_mixed_batch,         true  },
2021    { "top_p",           test_backend_top_p_sampling,          true  },
2022};
2023
2024static test_args parse_cli(int argc, char ** argv) {
2025    test_args out;
2026
2027    for (int i = 1; i < argc; ++i) {
2028        const char * arg = argv[i];
2029
2030        if (std::strcmp(arg, "--test") == 0) {
2031            if (i + 1 >= argc) {
2032                fprintf(stderr, "--test expects a value\n");
2033                exit(EXIT_FAILURE);
2034            }
2035            out.test = argv[++i];
2036            continue;
2037        }
2038        if (std::strncmp(arg, "--test=", 7) == 0) {
2039            out.test = arg + 7;
2040            continue;
2041        }
2042        if (std::strcmp(arg, "--model") == 0) {
2043            if (i + 1 >= argc) {
2044                fprintf(stderr, "--model expects a value\n");
2045                exit(EXIT_FAILURE);
2046            }
2047            out.model = argv[++i];
2048            continue;
2049        }
2050        if (std::strncmp(arg, "--model=", 8) == 0) {
2051            out.model = arg + 8;
2052            continue;
2053        }
2054        if (std::strcmp(arg, "--device") == 0) {
2055            if (i + 1 >= argc) {
2056                fprintf(stderr, "--device expects a value (cpu or gpu)\n");
2057                exit(EXIT_FAILURE);
2058            }
2059            out.device = argv[++i];
2060            continue;
2061        }
2062        if (std::strncmp(arg, "--device=", 9) == 0) {
2063            out.device = arg + 9;
2064            continue;
2065        }
2066        if (out.model.empty()) {
2067            out.model = arg;
2068            continue;
2069        }
2070
2071        fprintf(stderr, "Unexpected argument: %s\n", arg);
2072        exit(EXIT_FAILURE);
2073    }
2074
2075    if (out.device != "cpu" && out.device != "gpu" && out.device != "auto") {
2076        fprintf(stderr, "Invalid device '%s'. Must be 'cpu', 'gpu' or 'auto'\n", out.device.c_str());
2077        exit(EXIT_FAILURE);
2078    }
2079
2080    return out;
2081}
2082
2083static std::vector<const backend_test_case *> collect_tests_to_run(const std::string & requested) {
2084    std::vector<const backend_test_case *> selected;
2085
2086    if (!requested.empty()) {
2087        for (const auto & test : BACKEND_TESTS) {
2088            if (test.name == requested) {
2089                selected.push_back(&test);
2090                break;
2091            }
2092        }
2093        if (selected.empty()) {
2094            fprintf(stderr, "Unknown test '%s'. Available tests:\n", requested.c_str());
2095            for (const auto & test : BACKEND_TESTS) {
2096                fprintf(stderr, "  %s\n", test.name.c_str());
2097            }
2098            exit(EXIT_FAILURE);
2099        }
2100    } else {
2101        for (const auto & test : BACKEND_TESTS) {
2102            if (!test.enabled_by_default) {
2103                continue;
2104            }
2105#ifdef GGML_USE_HIP
2106            // TODO: remove this when https://github.com/ggml-org/llama.cpp/pull/26592 is merged
2107            if (test.name == "penalties" || test.name == "set_sampler" ||
2108                test.name == "mixed"     || test.name == "top_p"       ||
2109                test.name == "multi_output_sampling_chain" ||
2110                test.name == "multi_output_cpu") {
2111                fprintf(stderr, "Skipping test '%s' on HIP backend (no backend TOP_K support)\n", test.name.c_str());
2112                continue;
2113            }
2114#endif // GGML_USE_HIP
2115            selected.push_back(&test);
2116        }
2117    }
2118
2119    if (selected.empty()) {
2120        fprintf(stderr, "No backend sampling tests selected. Use --test=<name> to pick one.\n");
2121    }
2122
2123    return selected;
2124}
2125
2126static void run_tests(const std::vector<const backend_test_case *> & tests, const test_params & args) {
2127    for (const auto & test : tests) {
2128        fprintf(stderr, "\n=== %s ===\n", test->name.c_str());
2129        try {
2130            test->fn(args);
2131        } catch (const std::exception & e) {
2132            fprintf(stderr, "Error running test '%s': %s\n", test->name.c_str(), e.what());
2133            exit(EXIT_FAILURE);
2134        }
2135    }
2136}
2137
2138int main(int argc, char ** argv) {
2139    test_args args = parse_cli(argc, argv);
2140
2141    if (args.model.empty()) {
2142        args.model = common_get_model_or_exit(1, argv);
2143    }
2144
2145    {
2146        std::ifstream file(args.model);
2147        if (!file.is_open()) {
2148            fprintf(stderr, "no model '%s' found\n", args.model.c_str());
2149            return EXIT_FAILURE;
2150        }
2151    }
2152
2153    fprintf(stderr, "using '%s'\n", args.model.c_str());
2154
2155    llama_backend_init();
2156
2157    test_params params = {
2158        /*.model =*/ load_model(args),
2159    };
2160
2161    const std::vector<const backend_test_case *> tests = collect_tests_to_run(args.test);
2162    if (!tests.empty()) {
2163        run_tests(tests, params);
2164    }
2165
2166    return 0;
2167}