Coverage Report

Created: 2026-01-10 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/llama.cpp/ggml/src/ggml-backend-reg.cpp
Line
Count
Source
1
#include "ggml-backend-impl.h"
2
#include "ggml-backend.h"
3
#include "ggml-impl.h"
4
#include <algorithm>
5
#include <cstring>
6
#include <filesystem>
7
#include <memory>
8
#include <string>
9
#include <type_traits>
10
#include <vector>
11
#include <cctype>
12
13
#ifdef _WIN32
14
#    define WIN32_LEAN_AND_MEAN
15
#    ifndef NOMINMAX
16
#        define NOMINMAX
17
#    endif
18
#    include <windows.h>
19
#elif defined(__APPLE__)
20
#    include <mach-o/dyld.h>
21
#    include <dlfcn.h>
22
#else
23
#    include <dlfcn.h>
24
#    include <unistd.h>
25
#endif
26
27
// Backend registry
28
#ifdef GGML_USE_CPU
29
#include "ggml-cpu.h"
30
#endif
31
32
#ifdef GGML_USE_CUDA
33
#include "ggml-cuda.h"
34
#endif
35
36
#ifdef GGML_USE_METAL
37
#include "ggml-metal.h"
38
#endif
39
40
#ifdef GGML_USE_SYCL
41
#include "ggml-sycl.h"
42
#endif
43
44
#ifdef GGML_USE_VULKAN
45
#include "ggml-vulkan.h"
46
#endif
47
48
#ifdef GGML_USE_WEBGPU
49
#include "ggml-webgpu.h"
50
#endif
51
52
#ifdef GGML_USE_ZDNN
53
#include "ggml-zdnn.h"
54
#endif
55
56
#ifdef GGML_USE_OPENCL
57
#include "ggml-opencl.h"
58
#endif
59
60
#ifdef GGML_USE_HEXAGON
61
#include "ggml-hexagon.h"
62
#endif
63
64
#ifdef GGML_USE_BLAS
65
#include "ggml-blas.h"
66
#endif
67
68
#ifdef GGML_USE_RPC
69
#include "ggml-rpc.h"
70
#endif
71
72
#ifdef GGML_USE_CANN
73
#include "ggml-cann.h"
74
#endif
75
76
#ifdef GGML_USE_ZENDNN
77
#include "ggml-zendnn.h"
78
#endif
79
80
// disable C++17 deprecation warning for std::codecvt_utf8
81
#if defined(__clang__)
82
#    pragma clang diagnostic push
83
#    pragma clang diagnostic ignored "-Wdeprecated-declarations"
84
#elif defined(__GNUC__)
85
#    pragma GCC diagnostic push
86
#    pragma GCC diagnostic ignored "-Wdeprecated-declarations"
87
#endif
88
89
namespace fs = std::filesystem;
90
91
0
static std::string path_str(const fs::path & path) {
92
0
    std::string u8path;
93
0
    try {
94
#if defined(__cpp_lib_char8_t)
95
        // C++20 and later: u8string() returns std::u8string
96
        std::u8string u8str = path.u8string();
97
        u8path = std::string(reinterpret_cast<const char*>(u8str.c_str()));
98
#else
99
        // C++17: u8string() returns std::string
100
0
        u8path = path.u8string();
101
0
#endif
102
0
    } catch (...) {
103
0
    }
104
0
    return u8path;
105
0
}
106
107
#if defined(__clang__)
108
#    pragma clang diagnostic pop
109
#elif defined(__GNUC__)
110
#    pragma GCC diagnostic pop
111
#endif
112
113
#ifdef _WIN32
114
115
using dl_handle = std::remove_pointer_t<HMODULE>;
116
117
struct dl_handle_deleter {
118
    void operator()(HMODULE handle) {
119
        FreeLibrary(handle);
120
    }
121
};
122
123
static dl_handle * dl_load_library(const fs::path & path) {
124
    // suppress error dialogs for missing DLLs
125
    DWORD old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
126
    SetErrorMode(old_mode | SEM_FAILCRITICALERRORS);
127
128
    HMODULE handle = LoadLibraryW(path.wstring().c_str());
129
130
    SetErrorMode(old_mode);
131
132
    return handle;
133
}
134
135
static void * dl_get_sym(dl_handle * handle, const char * name) {
136
    DWORD old_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
137
    SetErrorMode(old_mode | SEM_FAILCRITICALERRORS);
138
139
    void * p = (void *) GetProcAddress(handle, name);
140
141
    SetErrorMode(old_mode);
142
143
    return p;
144
}
145
146
static const char * dl_error() {
147
    return "";
148
}
149
150
#else
151
152
using dl_handle = void;
153
154
struct dl_handle_deleter {
155
0
    void operator()(void * handle) {
156
0
        dlclose(handle);
157
0
    }
158
};
159
160
0
static void * dl_load_library(const fs::path & path) {
161
0
    dl_handle * handle = dlopen(path.string().c_str(), RTLD_NOW | RTLD_LOCAL);
162
163
0
    return handle;
164
0
}
165
166
0
static void * dl_get_sym(dl_handle * handle, const char * name) {
167
0
    return dlsym(handle, name);
168
0
}
169
170
0
static const char * dl_error() {
171
0
    const char *rslt = dlerror();
172
0
    return rslt != nullptr ? rslt : "";
173
0
}
174
175
#endif
176
177
using dl_handle_ptr = std::unique_ptr<dl_handle, dl_handle_deleter>;
178
179
struct ggml_backend_reg_entry {
180
    ggml_backend_reg_t reg;
181
    dl_handle_ptr handle;
182
};
183
184
struct ggml_backend_registry {
185
    std::vector<ggml_backend_reg_entry> backends;
186
    std::vector<ggml_backend_dev_t> devices;
187
188
0
    ggml_backend_registry() {
189
#ifdef GGML_USE_CUDA
190
        register_backend(ggml_backend_cuda_reg());
191
#endif
192
#ifdef GGML_USE_METAL
193
        register_backend(ggml_backend_metal_reg());
194
#endif
195
#ifdef GGML_USE_SYCL
196
        register_backend(ggml_backend_sycl_reg());
197
#endif
198
#ifdef GGML_USE_VULKAN
199
        register_backend(ggml_backend_vk_reg());
200
#endif
201
#ifdef GGML_USE_WEBGPU
202
        register_backend(ggml_backend_webgpu_reg());
203
#endif
204
#ifdef GGML_USE_ZDNN
205
        register_backend(ggml_backend_zdnn_reg());
206
#endif
207
#ifdef GGML_USE_OPENCL
208
        register_backend(ggml_backend_opencl_reg());
209
#endif
210
#ifdef GGML_USE_ZENDNN
211
        register_backend(ggml_backend_zendnn_reg());
212
#endif
213
#ifdef GGML_USE_HEXAGON
214
        register_backend(ggml_backend_hexagon_reg());
215
#endif
216
#ifdef GGML_USE_CANN
217
        register_backend(ggml_backend_cann_reg());
218
#endif
219
#ifdef GGML_USE_BLAS
220
        register_backend(ggml_backend_blas_reg());
221
#endif
222
#ifdef GGML_USE_RPC
223
        register_backend(ggml_backend_rpc_reg());
224
#endif
225
0
#ifdef GGML_USE_CPU
226
0
        register_backend(ggml_backend_cpu_reg());
227
0
#endif
228
0
    }
229
230
0
    ~ggml_backend_registry() {
231
        // FIXME: backends cannot be safely unloaded without a function to destroy all the backend resources,
232
        // since backend threads may still be running and accessing resources from the dynamic library
233
0
        for (auto & entry : backends) {
234
0
            if (entry.handle) {
235
0
                entry.handle.release(); // NOLINT
236
0
            }
237
0
        }
238
0
    }
239
240
0
    void register_backend(ggml_backend_reg_t reg, dl_handle_ptr handle = nullptr) {
241
0
        if (!reg) {
242
0
            return;
243
0
        }
244
245
#ifndef NDEBUG
246
        GGML_LOG_DEBUG("%s: registered backend %s (%zu devices)\n",
247
            __func__, ggml_backend_reg_name(reg), ggml_backend_reg_dev_count(reg));
248
#endif
249
0
        backends.push_back({ reg, std::move(handle) });
250
0
        for (size_t i = 0; i < ggml_backend_reg_dev_count(reg); i++) {
251
0
            register_device(ggml_backend_reg_dev_get(reg, i));
252
0
        }
253
0
    }
254
255
0
    void register_device(ggml_backend_dev_t device) {
256
#ifndef NDEBUG
257
        GGML_LOG_DEBUG("%s: registered device %s (%s)\n", __func__, ggml_backend_dev_name(device), ggml_backend_dev_description(device));
258
#endif
259
0
        devices.push_back(device);
260
0
    }
261
262
0
    ggml_backend_reg_t load_backend(const fs::path & path, bool silent) {
263
0
        dl_handle_ptr handle { dl_load_library(path) };
264
0
        if (!handle) {
265
0
            if (!silent) {
266
0
                GGML_LOG_ERROR("%s: failed to load %s: %s\n", __func__, path_str(path).c_str(), dl_error());
267
0
            }
268
0
            return nullptr;
269
0
        }
270
271
0
        auto score_fn = (ggml_backend_score_t) dl_get_sym(handle.get(), "ggml_backend_score");
272
0
        if (score_fn && score_fn() == 0) {
273
0
            if (!silent) {
274
0
                GGML_LOG_INFO("%s: backend %s is not supported on this system\n", __func__, path_str(path).c_str());
275
0
            }
276
0
            return nullptr;
277
0
        }
278
279
0
        auto backend_init_fn = (ggml_backend_init_t) dl_get_sym(handle.get(), "ggml_backend_init");
280
0
        if (!backend_init_fn) {
281
0
            if (!silent) {
282
0
                GGML_LOG_ERROR("%s: failed to find ggml_backend_init in %s\n", __func__, path_str(path).c_str());
283
0
            }
284
0
            return nullptr;
285
0
        }
286
287
0
        ggml_backend_reg_t reg = backend_init_fn();
288
0
        if (!reg || reg->api_version != GGML_BACKEND_API_VERSION) {
289
0
            if (!silent) {
290
0
                if (!reg) {
291
0
                    GGML_LOG_ERROR("%s: failed to initialize backend from %s: ggml_backend_init returned NULL\n",
292
0
                        __func__, path_str(path).c_str());
293
0
                } else {
294
0
                    GGML_LOG_ERROR("%s: failed to initialize backend from %s: incompatible API version (backend: %d, current: %d)\n",
295
0
                        __func__, path_str(path).c_str(), reg->api_version, GGML_BACKEND_API_VERSION);
296
0
                }
297
0
            }
298
0
            return nullptr;
299
0
        }
300
301
0
        GGML_LOG_INFO("%s: loaded %s backend from %s\n", __func__, ggml_backend_reg_name(reg), path_str(path).c_str());
302
303
0
        register_backend(reg, std::move(handle));
304
305
0
        return reg;
306
0
    }
307
308
0
    void unload_backend(ggml_backend_reg_t reg, bool silent) {
309
0
        auto it = std::find_if(backends.begin(), backends.end(),
310
0
                               [reg](const ggml_backend_reg_entry & entry) { return entry.reg == reg; });
311
312
0
        if (it == backends.end()) {
313
0
            if (!silent) {
314
0
                GGML_LOG_ERROR("%s: backend not found\n", __func__);
315
0
            }
316
0
            return;
317
0
        }
318
319
0
        if (!silent) {
320
0
            GGML_LOG_DEBUG("%s: unloading %s backend\n", __func__, ggml_backend_reg_name(reg));
321
0
        }
322
323
        // remove devices
324
0
        devices.erase(
325
0
            std::remove_if(devices.begin(), devices.end(),
326
0
                            [reg](ggml_backend_dev_t dev) { return ggml_backend_dev_backend_reg(dev) == reg; }),
327
0
            devices.end());
328
329
        // remove backend
330
0
        backends.erase(it);
331
0
    }
332
};
333
334
0
static ggml_backend_registry & get_reg() {
335
0
    static ggml_backend_registry reg;
336
0
    return reg;
337
0
}
338
339
// Internal API
340
0
void ggml_backend_register(ggml_backend_reg_t reg) {
341
0
    get_reg().register_backend(reg);
342
0
}
343
344
0
void ggml_backend_device_register(ggml_backend_dev_t device) {
345
0
    get_reg().register_device(device);
346
0
}
347
348
// Backend (reg) enumeration
349
0
static bool striequals(const char * a, const char * b) {
350
0
    for (; *a && *b; a++, b++) {
351
0
        if (std::tolower(*a) != std::tolower(*b)) {
352
0
            return false;
353
0
        }
354
0
    }
355
0
    return *a == *b;
356
0
}
357
358
0
size_t ggml_backend_reg_count() {
359
0
    return get_reg().backends.size();
360
0
}
361
362
0
ggml_backend_reg_t ggml_backend_reg_get(size_t index) {
363
0
    GGML_ASSERT(index < ggml_backend_reg_count());
364
0
    return get_reg().backends[index].reg;
365
0
}
366
367
0
ggml_backend_reg_t ggml_backend_reg_by_name(const char * name) {
368
0
    for (size_t i = 0; i < ggml_backend_reg_count(); i++) {
369
0
        ggml_backend_reg_t reg = ggml_backend_reg_get(i);
370
0
        if (striequals(ggml_backend_reg_name(reg), name)) {
371
0
            return reg;
372
0
        }
373
0
    }
374
0
    return nullptr;
375
0
}
376
377
// Device enumeration
378
0
size_t ggml_backend_dev_count() {
379
0
    return get_reg().devices.size();
380
0
}
381
382
0
ggml_backend_dev_t ggml_backend_dev_get(size_t index) {
383
0
    GGML_ASSERT(index < ggml_backend_dev_count());
384
0
    return get_reg().devices[index];
385
0
}
386
387
0
ggml_backend_dev_t ggml_backend_dev_by_name(const char * name) {
388
0
    for (size_t i = 0; i < ggml_backend_dev_count(); i++) {
389
0
        ggml_backend_dev_t dev = ggml_backend_dev_get(i);
390
0
        if (striequals(ggml_backend_dev_name(dev), name)) {
391
0
            return dev;
392
0
        }
393
0
    }
394
0
    return nullptr;
395
0
}
396
397
0
ggml_backend_dev_t ggml_backend_dev_by_type(enum ggml_backend_dev_type type) {
398
0
    for (size_t i = 0; i < ggml_backend_dev_count(); i++) {
399
0
        ggml_backend_dev_t dev = ggml_backend_dev_get(i);
400
0
        if (ggml_backend_dev_type(dev) == type) {
401
0
            return dev;
402
0
        }
403
0
    }
404
0
    return nullptr;
405
0
}
406
407
// Convenience functions
408
0
ggml_backend_t ggml_backend_init_by_name(const char * name, const char * params) {
409
0
    ggml_backend_dev_t dev = ggml_backend_dev_by_name(name);
410
0
    if (!dev) {
411
0
        return nullptr;
412
0
    }
413
0
    return ggml_backend_dev_init(dev, params);
414
0
}
415
416
0
ggml_backend_t ggml_backend_init_by_type(enum ggml_backend_dev_type type, const char * params) {
417
0
    ggml_backend_dev_t dev = ggml_backend_dev_by_type(type);
418
0
    if (!dev) {
419
0
        return nullptr;
420
0
    }
421
0
    return ggml_backend_dev_init(dev, params);
422
0
}
423
424
0
ggml_backend_t ggml_backend_init_best(void) {
425
0
    ggml_backend_dev_t dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU);
426
0
    dev = dev ? dev : ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_IGPU);
427
0
    dev = dev ? dev : ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
428
0
    if (!dev) {
429
0
        return nullptr;
430
0
    }
431
0
    return ggml_backend_dev_init(dev, nullptr);
432
0
}
433
434
// Dynamic loading
435
0
ggml_backend_reg_t ggml_backend_load(const char * path) {
436
0
    return get_reg().load_backend(path, false);
437
0
}
438
439
0
void ggml_backend_unload(ggml_backend_reg_t reg) {
440
0
    get_reg().unload_backend(reg, true);
441
0
}
442
443
0
static fs::path get_executable_path() {
444
#if defined(__APPLE__)
445
    // get executable path
446
    std::vector<char> path;
447
    uint32_t size;
448
    while (true) {
449
        size = path.size();
450
        if (_NSGetExecutablePath(path.data(), &size) == 0) {
451
            break;
452
        }
453
        path.resize(size);
454
    }
455
    std::string base_path(path.data(), size);
456
    // remove executable name
457
    auto last_slash = base_path.find_last_of('/');
458
    if (last_slash != std::string::npos) {
459
        base_path = base_path.substr(0, last_slash);
460
    }
461
    return base_path + "/";
462
#elif defined(__linux__) || defined(__FreeBSD__)
463
    std::string base_path = ".";
464
0
    std::vector<char> path(1024);
465
0
    while (true) {
466
        // get executable path
467
0
#    if defined(__linux__)
468
0
        ssize_t len = readlink("/proc/self/exe", path.data(), path.size());
469
#    elif defined(__FreeBSD__)
470
        ssize_t len = readlink("/proc/curproc/file", path.data(), path.size());
471
#    endif
472
0
        if (len == -1) {
473
0
            break;
474
0
        }
475
0
        if (len < (ssize_t) path.size()) {
476
0
            base_path = std::string(path.data(), len);
477
            // remove executable name
478
0
            auto last_slash = base_path.find_last_of('/');
479
0
            if (last_slash != std::string::npos) {
480
0
                base_path = base_path.substr(0, last_slash);
481
0
            }
482
0
            break;
483
0
        }
484
0
        path.resize(path.size() * 2);
485
0
    }
486
487
0
    return base_path + "/";
488
#elif defined(_WIN32)
489
    std::vector<wchar_t> path(MAX_PATH);
490
    DWORD len = GetModuleFileNameW(NULL, path.data(), path.size());
491
    if (len == 0) {
492
        return {};
493
    }
494
    std::wstring base_path(path.data(), len);
495
    // remove executable name
496
    auto last_slash = base_path.find_last_of('\\');
497
    if (last_slash != std::string::npos) {
498
        base_path = base_path.substr(0, last_slash);
499
    }
500
    return base_path + L"\\";
501
#else
502
    return {};
503
#endif
504
0
}
505
506
0
static fs::path backend_filename_prefix() {
507
#ifdef _WIN32
508
    return fs::u8path("ggml-");
509
#else
510
0
    return fs::u8path("libggml-");
511
0
#endif
512
0
}
513
514
0
static fs::path backend_filename_extension() {
515
#ifdef _WIN32
516
    return fs::u8path(".dll");
517
#else
518
0
    return fs::u8path(".so");
519
0
#endif
520
0
}
521
522
0
static ggml_backend_reg_t ggml_backend_load_best(const char * name, bool silent, const char * user_search_path) {
523
    // enumerate all the files that match [lib]ggml-name-*.[so|dll] in the search paths
524
0
    const fs::path name_path = fs::u8path(name);
525
0
    const fs::path file_prefix = backend_filename_prefix().native() + name_path.native() + fs::u8path("-").native();
526
0
    const fs::path file_extension = backend_filename_extension();
527
528
0
    std::vector<fs::path> search_paths;
529
0
    if (user_search_path == nullptr) {
530
#ifdef GGML_BACKEND_DIR
531
        search_paths.push_back(fs::u8path(GGML_BACKEND_DIR));
532
#endif
533
        // default search paths: executable directory, current directory
534
0
        search_paths.push_back(get_executable_path());
535
0
        search_paths.push_back(fs::current_path());
536
0
    } else {
537
0
        search_paths.push_back(fs::u8path(user_search_path));
538
0
    }
539
540
0
    int best_score = 0;
541
0
    fs::path best_path;
542
543
0
    for (const auto & search_path : search_paths) {
544
0
        if (std::error_code ec; !fs::exists(search_path, ec)) {
545
0
            if (ec) {
546
0
                GGML_LOG_DEBUG("%s: posix_stat(%s) failure, error-message: %s\n", __func__, path_str(search_path).c_str(), ec.message().c_str());
547
0
            } else {
548
0
                GGML_LOG_DEBUG("%s: search path %s does not exist\n", __func__, path_str(search_path).c_str());
549
0
            }
550
0
            continue;
551
0
        }
552
0
        fs::directory_iterator dir_it(search_path, fs::directory_options::skip_permission_denied);
553
0
        for (const auto & entry : dir_it) {
554
0
            if (entry.is_regular_file()) {
555
0
                auto filename = entry.path().filename();
556
0
                auto ext = entry.path().extension();
557
0
                if (filename.native().find(file_prefix) == 0 && ext == file_extension) {
558
0
                    dl_handle_ptr handle { dl_load_library(entry) };
559
0
                    if (!handle && !silent) {
560
0
                        GGML_LOG_ERROR("%s: failed to load %s: %s\n", __func__, path_str(entry.path()).c_str(), dl_error());
561
0
                    }
562
0
                    if (handle) {
563
0
                        auto score_fn = (ggml_backend_score_t) dl_get_sym(handle.get(), "ggml_backend_score");
564
0
                        if (score_fn) {
565
0
                            int s = score_fn();
566
#ifndef NDEBUG
567
                            GGML_LOG_DEBUG("%s: %s score: %d\n", __func__, path_str(entry.path()).c_str(), s);
568
#endif
569
0
                            if (s > best_score) {
570
0
                                best_score = s;
571
0
                                best_path = entry.path();
572
0
                            }
573
0
                        } else {
574
0
                            if (!silent) {
575
0
                                GGML_LOG_INFO("%s: failed to find ggml_backend_score in %s\n", __func__, path_str(entry.path()).c_str());
576
0
                            }
577
0
                        }
578
0
                    }
579
0
                }
580
0
            }
581
0
        }
582
0
    }
583
584
0
    if (best_score == 0) {
585
        // try to load the base backend
586
0
        for (const auto & search_path : search_paths) {
587
0
            fs::path filename = backend_filename_prefix().native() + name_path.native() + backend_filename_extension().native();
588
0
            fs::path path = search_path / filename;
589
0
            if (std::error_code ec; fs::exists(path, ec)) {
590
0
                return get_reg().load_backend(path, silent);
591
0
            } else {
592
0
                if (ec) {
593
0
                    GGML_LOG_DEBUG("%s: posix_stat(%s) failure, error-message: %s\n", __func__, path_str(path).c_str(), ec.message().c_str());
594
0
                }
595
0
            }
596
0
        }
597
0
        return nullptr;
598
0
    }
599
600
0
    return get_reg().load_backend(best_path, silent);
601
0
}
602
603
0
void ggml_backend_load_all() {
604
0
    ggml_backend_load_all_from_path(nullptr);
605
0
}
606
607
0
void ggml_backend_load_all_from_path(const char * dir_path) {
608
0
#ifdef NDEBUG
609
0
    bool silent = true;
610
#else
611
    bool silent = false;
612
#endif
613
614
0
    ggml_backend_load_best("blas", silent, dir_path);
615
0
    ggml_backend_load_best("zendnn", silent, dir_path);
616
0
    ggml_backend_load_best("cann", silent, dir_path);
617
0
    ggml_backend_load_best("cuda", silent, dir_path);
618
0
    ggml_backend_load_best("hip", silent, dir_path);
619
0
    ggml_backend_load_best("metal", silent, dir_path);
620
0
    ggml_backend_load_best("rpc", silent, dir_path);
621
0
    ggml_backend_load_best("sycl", silent, dir_path);
622
0
    ggml_backend_load_best("vulkan", silent, dir_path);
623
0
    ggml_backend_load_best("opencl", silent, dir_path);
624
0
    ggml_backend_load_best("hexagon", silent, dir_path);
625
0
    ggml_backend_load_best("musa", silent, dir_path);
626
0
    ggml_backend_load_best("cpu", silent, dir_path);
627
    // check the environment variable GGML_BACKEND_PATH to load an out-of-tree backend
628
0
    const char * backend_path = std::getenv("GGML_BACKEND_PATH");
629
0
    if (backend_path) {
630
0
        ggml_backend_load(backend_path);
631
0
    }
632
0
}