Coverage Report

Created: 2026-02-01 07:21

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