Coverage Report

Created: 2026-07-16 06:35

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