Coverage Report

Created: 2026-06-22 06:47

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