Coverage Report

Created: 2025-11-24 06:10

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