Coverage Report

Created: 2026-04-12 06:40

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