char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Georgi Gerganovmetal : single-source fusion table + fusion debug rework (#28164)a2878d30d

char/aliceai-t5-moe
37.9 KiB1048 linesraw
1#include "ggml-metal.h"
2
3#include "ggml-impl.h"
4#include "ggml-backend-impl.h"
5
6#include "ggml-metal-device.h"
7#include "ggml-metal-fusion.h"
8#include "ggml-metal-context.h"
9#include "ggml-metal-ops.h"
10#include "ggml-metal-tuning.h"
11
12#include <mutex>
13#include <string>
14
15#define GGML_METAL_NAME "MTL"
16#define GGML_METAL_MAX_DEVICES 16
17
18// number of Metal devices
19// note: can be overridden with GGML_METAL_DEVICES env to simulate virtual devices
20static int g_devices = 1;
21
22// forward declaration
23static bool ggml_backend_buffer_is_metal(ggml_backend_buffer_t buffer);
24
25////////////////////////////////////////////////////////////////////////////////
26// backend interface
27////////////////////////////////////////////////////////////////////////////////
28
29// shared buffer
30
31static void ggml_backend_metal_buffer_shared_free_buffer(ggml_backend_buffer_t buffer) {
32    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
33
34    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
35
36    ggml_metal_buffer_free(ctx);
37}
38
39static void * ggml_backend_metal_buffer_shared_get_base(ggml_backend_buffer_t buffer) {
40    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
41
42    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
43
44    return ggml_metal_buffer_get_base(ctx);
45}
46
47static void ggml_backend_metal_buffer_shared_memset_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, uint8_t value, size_t offset, size_t size) {
48    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
49
50    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
51
52    ggml_metal_buffer_memset_tensor(ctx, tensor, value, offset, size);
53}
54
55static void ggml_backend_metal_buffer_shared_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
56    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
57
58    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
59
60    ggml_metal_buffer_set_tensor(ctx, tensor, data, offset, size);
61}
62
63static void ggml_backend_metal_buffer_shared_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) {
64    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
65
66    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
67
68    ggml_metal_buffer_get_tensor(ctx, tensor, data, offset, size);
69}
70
71static bool ggml_backend_metal_buffer_shared_cpy_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * src, ggml_tensor * dst) {
72    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
73
74    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
75
76    if (!ggml_backend_buffer_is_metal(src->buffer)) {
77        return false;
78    }
79
80    return ggml_metal_buffer_cpy_tensor(ctx, src, dst);
81}
82
83static void ggml_backend_metal_buffer_shared_clear(ggml_backend_buffer_t buffer, uint8_t value) {
84    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
85
86    GGML_ASSERT(ggml_metal_buffer_is_shared(ctx));
87
88    ggml_metal_buffer_clear(ctx, value);
89}
90
91static ggml_backend_buffer_i ggml_backend_metal_buffer_shared_i = {
92    /* .free_buffer   = */ ggml_backend_metal_buffer_shared_free_buffer,
93    /* .get_base      = */ ggml_backend_metal_buffer_shared_get_base,
94    /* .init_tensor   = */ NULL,
95    /* .memset_tensor = */ ggml_backend_metal_buffer_shared_memset_tensor,
96    /* .set_tensor    = */ ggml_backend_metal_buffer_shared_set_tensor,
97    /* .get_tensor    = */ ggml_backend_metal_buffer_shared_get_tensor,
98    /* .set_tensor_2d = */ NULL,
99    /* .get_tensor_2d = */ NULL,
100    /* .cpy_tensor    = */ ggml_backend_metal_buffer_shared_cpy_tensor,
101    /* .clear         = */ ggml_backend_metal_buffer_shared_clear,
102    /* .reset         = */ NULL,
103};
104
105// private buffer
106
107static void ggml_backend_metal_buffer_private_free_buffer(ggml_backend_buffer_t buffer) {
108    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
109
110    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
111
112    ggml_metal_buffer_free(ctx);
113}
114
115static void * ggml_backend_metal_buffer_private_get_base(ggml_backend_buffer_t buffer) {
116    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
117
118    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
119
120    return ggml_metal_buffer_get_base(ctx);
121}
122
123static void ggml_backend_metal_buffer_private_memset_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, uint8_t value, size_t offset, size_t size) {
124    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
125
126    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
127
128    ggml_metal_buffer_memset_tensor(ctx, tensor, value, offset, size);
129}
130
131static void ggml_backend_metal_buffer_private_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
132    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
133
134    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
135
136    ggml_metal_buffer_set_tensor(ctx, tensor, data, offset, size);
137}
138
139static void ggml_backend_metal_buffer_private_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) {
140    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
141
142    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
143
144    ggml_metal_buffer_get_tensor(ctx, tensor, data, offset, size);
145}
146
147static bool ggml_backend_metal_buffer_private_cpy_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * src, ggml_tensor * dst) {
148    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
149
150    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
151
152    if (!ggml_backend_buffer_is_metal(src->buffer)) {
153        return false;
154    }
155
156    return ggml_metal_buffer_cpy_tensor(ctx, src, dst);
157}
158
159static void ggml_backend_metal_buffer_private_clear(ggml_backend_buffer_t buffer, uint8_t value) {
160    ggml_metal_buffer_t ctx = (ggml_metal_buffer_t)buffer->context;
161
162    GGML_ASSERT(!ggml_metal_buffer_is_shared(ctx));
163
164    ggml_metal_buffer_clear(ctx, value);
165}
166
167static ggml_backend_buffer_i ggml_backend_metal_buffer_private_i = {
168    /* .free_buffer   = */ ggml_backend_metal_buffer_private_free_buffer,
169    /* .get_base      = */ ggml_backend_metal_buffer_private_get_base,
170    /* .init_tensor   = */ NULL,
171    /* .memset_tensor = */ ggml_backend_metal_buffer_private_memset_tensor,
172    /* .set_tensor    = */ ggml_backend_metal_buffer_private_set_tensor,
173    /* .get_tensor    = */ ggml_backend_metal_buffer_private_get_tensor,
174    /* .set_tensor_2d = */ NULL,
175    /* .get_tensor_2d = */ NULL,
176    /* .cpy_tensor    = */ ggml_backend_metal_buffer_private_cpy_tensor,
177    /* .clear         = */ ggml_backend_metal_buffer_private_clear,
178    /* .reset         = */ NULL,
179};
180
181static bool ggml_backend_buffer_is_metal(ggml_backend_buffer_t buffer) {
182    return buffer->iface.free_buffer == ggml_backend_metal_buffer_shared_free_buffer ||
183           buffer->iface.free_buffer == ggml_backend_metal_buffer_private_free_buffer;
184}
185
186//
187// buffer types
188//
189
190struct ggml_backend_metal_buffer_type {
191    int device;
192    std::string name;
193};
194
195struct ggml_backend_metal_buffer_type_deleter {
196    void operator()(ggml_backend_metal_buffer_type * ctx) const {
197        delete ctx;
198    }
199};
200
201typedef std::unique_ptr<ggml_backend_metal_buffer_type, ggml_backend_metal_buffer_type_deleter> ggml_backend_metal_buffer_type_ptr;
202
203// common method for allocating shread or private Metal buffers
204static ggml_backend_buffer_t ggml_backend_metal_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size, bool shared) {
205    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)buft->device->context;
206    ggml_metal_buffer_t res = ggml_metal_buffer_init(ctx_dev, size, shared);
207
208    if (res == NULL) {
209        GGML_LOG_ERROR("%s: failed to allocate Metal buffer of %zu bytes (out of memory)\n", __func__, size);
210        return NULL;
211    }
212
213    ggml_backend_buffer_i buf_i = ggml_metal_buffer_is_shared(res)
214        ? ggml_backend_metal_buffer_shared_i
215        : ggml_backend_metal_buffer_private_i;
216
217    return ggml_backend_buffer_init(buft, buf_i, res, size);
218}
219
220static size_t ggml_backend_metal_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
221    size_t res = ggml_nbytes(tensor);
222
223    // some operations require additional memory for fleeting data:
224    switch (tensor->op) {
225        case GGML_OP_MUL_MAT_ID:
226            {
227                res += ggml_metal_op_mul_mat_id_extra_tpe(tensor);
228                res += ggml_metal_op_mul_mat_id_extra_ids(tensor);
229            } break;
230        case GGML_OP_FLASH_ATTN_EXT:
231            {
232                res += ggml_metal_op_flash_attn_ext_extra_pad(tensor);
233                res += ggml_metal_op_flash_attn_ext_extra_blk(tensor);
234                res += ggml_metal_op_flash_attn_ext_extra_tmp(tensor);
235                res += ggml_metal_op_flash_attn_ext_extra_kv_f16(tensor);
236                res += ggml_metal_op_flash_attn_ext_extra_idx(tensor);
237            } break;
238        case GGML_OP_CUMSUM:
239        case GGML_OP_ARGSORT:
240            {
241                res *= 2;
242            } break;
243        case GGML_OP_TOP_K:
244            {
245                res = 2*sizeof(int32_t)*ggml_nelements(tensor->src[0]);
246            } break;
247        default:
248            break;
249    }
250
251    return res;
252
253    GGML_UNUSED(buft);
254}
255
256// default (shared) buffer type
257
258static const char * ggml_backend_metal_buffer_type_shared_get_name(ggml_backend_buffer_type_t buft) {
259    ggml_backend_metal_buffer_type * ctx = (ggml_backend_metal_buffer_type *)buft->context;
260
261    return ctx->name.c_str();
262}
263
264static ggml_backend_buffer_t ggml_backend_metal_buffer_type_shared_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
265    return ggml_backend_metal_buffer_type_alloc_buffer(buft, size, true);
266}
267
268static size_t ggml_backend_metal_buffer_type_shared_get_alignment(ggml_backend_buffer_type_t buft) {
269    return 32;
270
271    GGML_UNUSED(buft);
272}
273
274static size_t ggml_backend_metal_buffer_type_shared_get_max_size(ggml_backend_buffer_type_t buft) {
275    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)buft->device->context;
276
277    return ggml_metal_device_get_props(ctx_dev)->max_buffer_size;
278}
279
280static size_t ggml_backend_metal_buffer_type_shared_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
281    return ggml_backend_metal_buffer_type_get_alloc_size(buft, tensor);
282}
283
284static bool ggml_backend_metal_buffer_type_shared_is_host(ggml_backend_buffer_type_t buft) {
285    return false;
286
287    GGML_UNUSED(buft);
288}
289
290static ggml_backend_buffer_type_t ggml_backend_metal_buffer_type_shared(int device) {
291    static std::mutex mutex;
292    std::lock_guard<std::mutex> lock(mutex);
293
294    static std::vector<ggml_backend_buffer_type> bufts;
295    static std::vector<ggml_backend_metal_buffer_type_ptr> ctxs;
296
297    static bool initialized = false;
298    if (!initialized) {
299        bufts.reserve(g_devices);
300        ctxs.reserve(g_devices);
301
302        for (int i = 0; i < g_devices; ++i) {
303            ggml_backend_metal_buffer_type * raw_ctx =
304                new ggml_backend_metal_buffer_type {
305                    /* .device = */ i,
306                    /* .name   = */ GGML_METAL_NAME + std::to_string(i),
307                };
308            ctxs.emplace_back(raw_ctx);
309
310            ggml_backend_buffer_type buft = {
311                /* .iface = */ {
312                    /* .get_name         = */ ggml_backend_metal_buffer_type_shared_get_name,
313                    /* .alloc_buffer     = */ ggml_backend_metal_buffer_type_shared_alloc_buffer,
314                    /* .get_alignment    = */ ggml_backend_metal_buffer_type_shared_get_alignment,
315                    /* .get_max_size     = */ ggml_backend_metal_buffer_type_shared_get_max_size,
316                    /* .get_alloc_size   = */ ggml_backend_metal_buffer_type_shared_get_alloc_size,
317                    /* .is_host          = */ ggml_backend_metal_buffer_type_shared_is_host,
318                },
319                /* .device  = */ ggml_backend_reg_dev_get(ggml_backend_metal_reg(), i),
320                /* .context = */ raw_ctx,
321            };
322
323            bufts.emplace_back(buft);
324        }
325
326        initialized = true;
327    }
328
329    return &bufts[device];
330}
331
332// default (private) buffer type
333
334static const char * ggml_backend_metal_buffer_type_private_get_name(ggml_backend_buffer_type_t buft) {
335    ggml_backend_metal_buffer_type * ctx = (ggml_backend_metal_buffer_type *)buft->context;
336
337    return ctx->name.c_str();
338}
339
340static ggml_backend_buffer_t ggml_backend_metal_buffer_type_private_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
341    return ggml_backend_metal_buffer_type_alloc_buffer(buft, size, false);
342}
343
344static size_t ggml_backend_metal_buffer_type_private_get_alignment(ggml_backend_buffer_type_t buft) {
345    return 32;
346
347    GGML_UNUSED(buft);
348}
349
350static size_t ggml_backend_metal_buffer_type_private_get_max_size(ggml_backend_buffer_type_t buft) {
351    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)buft->device->context;
352
353    return ggml_metal_device_get_props(ctx_dev)->max_buffer_size;
354}
355
356static size_t ggml_backend_metal_buffer_type_private_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
357    return ggml_backend_metal_buffer_type_get_alloc_size(buft, tensor);
358}
359
360static bool ggml_backend_metal_buffer_type_private_is_host(ggml_backend_buffer_type_t buft) {
361    return false;
362
363    GGML_UNUSED(buft);
364}
365
366static ggml_backend_buffer_type_t ggml_backend_metal_buffer_type_private(int device) {
367    static std::mutex mutex;
368    std::lock_guard<std::mutex> lock(mutex);
369
370    static std::vector<ggml_backend_buffer_type> bufts;
371    static std::vector<ggml_backend_metal_buffer_type_ptr> ctxs;
372
373    static bool initialized = false;
374    if (!initialized) {
375        bufts.reserve(g_devices);
376        ctxs.reserve(g_devices);
377
378        for (int i = 0; i < g_devices; ++i) {
379            ggml_backend_metal_buffer_type * raw_ctx = new ggml_backend_metal_buffer_type{
380                /* .device = */ i,
381                /* .name   = */ GGML_METAL_NAME + std::to_string(i) + "_Private"
382            };
383            ctxs.emplace_back(raw_ctx);
384
385            ggml_backend_buffer_type buft = {
386                /* .iface = */ {
387                    /* .get_name         = */ ggml_backend_metal_buffer_type_private_get_name,
388                    /* .alloc_buffer     = */ ggml_backend_metal_buffer_type_private_alloc_buffer,
389                    /* .get_alignment    = */ ggml_backend_metal_buffer_type_private_get_alignment,
390                    /* .get_max_size     = */ ggml_backend_metal_buffer_type_private_get_max_size,
391                    /* .get_alloc_size   = */ ggml_backend_metal_buffer_type_private_get_alloc_size,
392                    /* .is_host          = */ ggml_backend_metal_buffer_type_private_is_host,
393                },
394                /* .device  = */ ggml_backend_reg_dev_get(ggml_backend_metal_reg(), i),
395                /* .context = */ raw_ctx,
396            };
397
398            bufts.emplace_back(buft);
399        }
400
401        initialized = true;
402    }
403
404    return &bufts[device];
405}
406
407// mapped buffer type
408
409static const char * ggml_backend_metal_buffer_type_mapped_get_name(ggml_backend_buffer_type_t buft) {
410    ggml_backend_metal_buffer_type * ctx = (ggml_backend_metal_buffer_type *)buft->context;
411
412    return ctx->name.c_str();
413}
414
415static ggml_backend_buffer_t ggml_backend_metal_buffer_type_mapped_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
416    // for mapped buffers, prefer shared memory
417    return ggml_backend_metal_buffer_type_alloc_buffer(buft, size, true);
418}
419
420static size_t ggml_backend_metal_buffer_type_mapped_get_alignment(ggml_backend_buffer_type_t buft) {
421    return 32;
422
423    GGML_UNUSED(buft);
424}
425
426static size_t ggml_backend_metal_buffer_type_mapped_get_max_size(ggml_backend_buffer_type_t buft) {
427    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)buft->device->context;
428
429    return ggml_metal_device_get_props(ctx_dev)->max_buffer_size;
430}
431
432static size_t ggml_backend_metal_buffer_type_mapped_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
433    return ggml_backend_metal_buffer_type_get_alloc_size(buft, tensor);
434}
435
436static bool ggml_backend_metal_buffer_type_mapped_is_host(ggml_backend_buffer_type_t buft) {
437    return false;
438
439    GGML_UNUSED(buft);
440}
441
442static ggml_backend_buffer_type_t ggml_backend_metal_buffer_type_mapped(int device) {
443    static std::mutex mutex;
444    std::lock_guard<std::mutex> lock(mutex);
445
446    static std::vector<ggml_backend_buffer_type> bufts;
447    static std::vector<ggml_backend_metal_buffer_type_ptr> ctxs;
448
449    static bool initialized = false;
450    if (!initialized) {
451        bufts.reserve(g_devices);
452        ctxs.reserve(g_devices);
453
454        for (int i = 0; i < g_devices; ++i) {
455            ggml_backend_metal_buffer_type * raw_ctx = new ggml_backend_metal_buffer_type{
456                /* .device = */ i,
457                /* .name   = */ GGML_METAL_NAME + std::to_string(i) + "_Mapped"
458            };
459            ctxs.emplace_back(raw_ctx);
460
461            // note: not obvious, but this buffer type still needs to implement .alloc_buffer:
462            //       https://github.com/ggml-org/llama.cpp/pull/15832#discussion_r2333177099
463            ggml_backend_buffer_type buft = {
464                /* .iface = */ {
465                    /* .get_name         = */ ggml_backend_metal_buffer_type_mapped_get_name,
466                    /* .alloc_buffer     = */ ggml_backend_metal_buffer_type_mapped_alloc_buffer,
467                    /* .get_alignment    = */ ggml_backend_metal_buffer_type_mapped_get_alignment,
468                    /* .get_max_size     = */ ggml_backend_metal_buffer_type_mapped_get_max_size,
469                    /* .get_alloc_size   = */ ggml_backend_metal_buffer_type_mapped_get_alloc_size,
470                    /* .is_host          = */ ggml_backend_metal_buffer_type_mapped_is_host,
471                },
472                /* .device  = */ ggml_backend_reg_dev_get(ggml_backend_metal_reg(), i),
473                /* .context = */ raw_ctx,
474            };
475
476            bufts.emplace_back(buft);
477        }
478
479        initialized = true;
480    }
481
482    return &bufts[device];
483}
484
485// backend
486
487static const char * ggml_backend_metal_name(ggml_backend_t backend) {
488    ggml_metal_t ctx = (ggml_metal_t)backend->context;
489
490    return ggml_metal_get_name(ctx);
491}
492
493static void ggml_backend_metal_free(ggml_backend_t backend) {
494    ggml_metal_t ctx = (ggml_metal_t)backend->context;
495
496    // wait for any ongoing async operations to finish
497    ggml_metal_synchronize(ctx);
498
499    ggml_metal_free(ctx);
500
501    free(backend);
502}
503
504static void ggml_backend_metal_synchronize(ggml_backend_t backend) {
505    ggml_metal_t ctx = (ggml_metal_t)backend->context;
506
507    ggml_metal_synchronize(ctx);
508}
509
510static void ggml_backend_metal_set_tensor_async(ggml_backend_t backend, ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
511    ggml_metal_t ctx = (ggml_metal_t)backend->context;
512
513    ggml_metal_set_tensor_async(ctx, tensor, data, offset, size);
514}
515
516static void ggml_backend_metal_get_tensor_async(ggml_backend_t backend, const ggml_tensor * tensor, void * data, size_t offset, size_t size) {
517    ggml_metal_t ctx = (ggml_metal_t)backend->context;
518
519    ggml_metal_get_tensor_async(ctx, tensor, data, offset, size);
520}
521
522static bool ggml_backend_metal_cpy_tensor_async(ggml_backend_t backend_src, ggml_backend_t backend_dst, const ggml_tensor * src, ggml_tensor * dst) {
523    if (!ggml_backend_is_metal(backend_src) || !ggml_backend_is_metal(backend_dst)) {
524        return false;
525    }
526
527    if (!ggml_backend_buffer_is_metal(src->buffer) || !ggml_backend_buffer_is_metal(dst->buffer)) {
528        return false;
529    }
530
531    ggml_metal_t ctx_src = (ggml_metal_t)backend_src->context;
532    ggml_metal_t ctx_dst = (ggml_metal_t)backend_dst->context;
533
534    //ggml_backend_buffer_t buf_src = src->view_src ? src->view_src->buffer : src->buffer;
535    //ggml_backend_buffer_t buf_dst = dst->view_src ? dst->view_src->buffer : dst->buffer;
536
537    //ggml_metal_buffer_t buf_ctx_src = (ggml_metal_buffer_t)buf_src->context;
538    //ggml_metal_buffer_t buf_ctx_dst = (ggml_metal_buffer_t)buf_dst->context;
539
540    return ggml_metal_cpy_tensor_async(ctx_src, ctx_dst, src, dst);
541}
542
543static enum ggml_status ggml_backend_metal_graph_compute(ggml_backend_t backend, ggml_cgraph * cgraph) {
544    ggml_metal_t ctx = (ggml_metal_t)backend->context;
545
546    return ggml_metal_graph_compute(ctx, cgraph);
547}
548
549static void ggml_backend_metal_event_record(ggml_backend_t backend, ggml_backend_event_t event) {
550    ggml_metal_t ctx = (ggml_metal_t)backend->context;
551    ggml_metal_event_t ev = (ggml_metal_event_t)event->context;
552
553    ggml_metal_event_record(ctx, ev);
554}
555
556static void ggml_backend_metal_event_wait(ggml_backend_t backend, ggml_backend_event_t event) {
557    ggml_metal_t ctx = (ggml_metal_t)backend->context;
558    ggml_metal_event_t ev = (ggml_metal_event_t)event->context;
559
560    ggml_metal_event_wait(ctx, ev);
561}
562
563static void ggml_backend_metal_graph_optimize(ggml_backend_t backend, ggml_cgraph * cgraph, ggml_backend_graph_optimize_params * params) {
564    GGML_UNUSED(params);
565
566    ggml_metal_t ctx = (ggml_metal_t)backend->context;
567
568    ggml_metal_graph_optimize(ctx, cgraph);
569}
570
571static void ggml_backend_metal_set_n_cb(ggml_backend_t backend, int n_cb) {
572    GGML_ASSERT(ggml_backend_is_metal(backend));
573
574    ggml_metal_t ctx = (ggml_metal_t)backend->context;
575
576    ggml_metal_set_n_cb(ctx, n_cb);
577}
578
579static ggml_backend_i ggml_backend_metal_i = {
580    /* .get_name                = */ ggml_backend_metal_name,
581    /* .free                    = */ ggml_backend_metal_free,
582    /* .set_tensor_async        = */ ggml_backend_metal_set_tensor_async,
583    /* .get_tensor_async        = */ ggml_backend_metal_get_tensor_async,
584    /* .set_tensor_2d_async     = */ NULL,
585    /* .get_tensor_2d_async     = */ NULL,
586    /* .cpy_tensor_async        = */ ggml_backend_metal_cpy_tensor_async, // only needed for multi-GPU setups
587    /* .synchronize             = */ ggml_backend_metal_synchronize,
588    /* .graph_plan_create       = */ NULL,
589    /* .graph_plan_free         = */ NULL,
590    /* .graph_plan_update       = */ NULL,
591    /* .graph_plan_compute      = */ NULL,
592    /* .graph_compute           = */ ggml_backend_metal_graph_compute,
593    /* .event_record            = */ ggml_backend_metal_event_record,
594    /* .event_wait              = */ ggml_backend_metal_event_wait,
595    /* .graph_optimize          = */ ggml_backend_metal_graph_optimize,
596};
597
598static ggml_guid_t ggml_backend_metal_guid(void) {
599    static ggml_guid guid = { 0x81, 0xa1, 0x8b, 0x1e, 0x71, 0xec, 0x79, 0xed, 0x2b, 0x85, 0xdc, 0x8a, 0x61, 0x98, 0x30, 0xe6 };
600    return &guid;
601}
602
603ggml_backend_t ggml_backend_metal_init(void) {
604    ggml_backend_dev_t dev = ggml_backend_reg_dev_get(ggml_backend_metal_reg(), 0);
605    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
606
607    ggml_metal_t ctx = ggml_metal_init(ctx_dev);
608    if (ctx == NULL) {
609        GGML_LOG_ERROR("%s: error: failed to allocate context\n", __func__);
610        return NULL;
611    }
612
613    ggml_backend_t backend = (ggml_backend_t) malloc(sizeof(ggml_backend));
614
615    *backend = {
616        /* .guid      = */ ggml_backend_metal_guid(),
617        /* .interface = */ ggml_backend_metal_i,
618        /* .device    = */ dev,
619        /* .context   = */ ctx,
620    };
621
622    ggml_backend_metal_set_n_cb(backend, 1);
623
624    return backend;
625}
626
627bool ggml_backend_is_metal(ggml_backend_t backend) {
628    return backend != NULL && ggml_guid_matches(backend->guid, ggml_backend_metal_guid());
629}
630
631void ggml_backend_metal_set_abort_callback(ggml_backend_t backend, ggml_abort_callback abort_callback, void * user_data) {
632    GGML_ASSERT(ggml_backend_is_metal(backend));
633
634    ggml_metal_t ctx = (ggml_metal_t)backend->context;
635
636    ggml_metal_set_abort_callback(ctx, abort_callback, user_data);
637}
638
639bool ggml_backend_metal_supports_family(ggml_backend_t backend, int family) {
640    GGML_ASSERT(ggml_backend_is_metal(backend));
641
642    ggml_metal_t ctx = (ggml_metal_t)backend->context;
643
644    return ggml_metal_supports_family(ctx, family);
645}
646
647void ggml_backend_metal_capture_next_compute(ggml_backend_t backend) {
648    GGML_ASSERT(ggml_backend_is_metal(backend));
649
650    ggml_metal_t ctx = (ggml_metal_t)backend->context;
651
652    ggml_metal_capture_next_compute(ctx);
653}
654
655// backend device
656
657static const char * ggml_backend_metal_device_get_name(ggml_backend_dev_t dev) {
658    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
659
660    const ggml_metal_device_props * props_dev = ggml_metal_device_get_props(ctx_dev);
661
662    return props_dev->name;
663}
664
665static const char * ggml_backend_metal_device_get_description(ggml_backend_dev_t dev) {
666    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
667
668    return ggml_metal_device_get_props(ctx_dev)->desc;
669}
670
671static void ggml_backend_metal_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) {
672    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
673
674    ggml_metal_device_get_memory(ctx_dev, free, total);
675}
676
677static enum ggml_backend_dev_type ggml_backend_metal_device_get_type(ggml_backend_dev_t dev) {
678    return GGML_BACKEND_DEVICE_TYPE_GPU;
679
680    GGML_UNUSED(dev);
681}
682
683static void ggml_backend_metal_device_get_props(ggml_backend_dev_t dev, ggml_backend_dev_props * props) {
684    props->name        = ggml_backend_metal_device_get_name(dev);
685    props->description = ggml_backend_metal_device_get_description(dev);
686    props->type        = ggml_backend_metal_device_get_type(dev);
687
688    ggml_backend_metal_device_get_memory(dev, &props->memory_free, &props->memory_total);
689
690    props->caps = {
691        /* .async                = */ true,
692        /* .host_buffer          = */ false,
693        /* .buffer_from_host_ptr = */ true,
694        /* .events               = */ true,
695        /* .mmap_support         = */ true,
696    };
697}
698
699static ggml_backend_t ggml_backend_metal_device_init_backend(ggml_backend_dev_t dev, const char * params) {
700    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
701
702    ggml_metal_t ctx = ggml_metal_init(ctx_dev);
703    if (ctx == NULL) {
704        GGML_LOG_ERROR("%s: error: failed to allocate context\n", __func__);
705        return NULL;
706    }
707
708    ggml_backend_t backend = (ggml_backend_t) malloc(sizeof(ggml_backend));
709
710    *backend = {
711        /* .guid      = */ ggml_backend_metal_guid(),
712        /* .interface = */ ggml_backend_metal_i,
713        /* .device    = */ dev,
714        /* .context   = */ ctx,
715    };
716
717    ggml_backend_metal_set_n_cb(backend, 1);
718
719    return backend;
720
721    GGML_UNUSED(params);
722}
723
724static ggml_backend_buffer_type_t ggml_backend_metal_device_get_buffer_type(ggml_backend_dev_t dev) {
725    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
726
727    const ggml_metal_device_props * props_dev = ggml_metal_device_get_props(ctx_dev);
728
729    return props_dev->use_shared_buffers ? ggml_backend_metal_buffer_type_shared(props_dev->device) : ggml_backend_metal_buffer_type_private(props_dev->device);
730}
731
732static ggml_backend_buffer_t ggml_backend_metal_device_buffer_mapped(ggml_backend_dev_t dev, void * ptr, size_t size, size_t max_tensor_size) {
733    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
734
735    ggml_metal_buffer_t res = ggml_metal_buffer_map(ctx_dev, ptr, size, max_tensor_size);
736
737    const ggml_metal_device_props * props_dev = ggml_metal_device_get_props(ctx_dev);
738
739    return ggml_backend_buffer_init(ggml_backend_metal_buffer_type_mapped(props_dev->device), ggml_backend_metal_buffer_shared_i, res, size);
740}
741
742static bool ggml_backend_metal_device_supports_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
743    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
744
745    return ggml_metal_device_supports_op(ctx_dev, op);
746}
747
748static bool ggml_backend_metal_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) {
749    return
750        buft->device == dev && (
751        buft->iface.get_name == ggml_backend_metal_buffer_type_shared_get_name ||
752        buft->iface.get_name == ggml_backend_metal_buffer_type_private_get_name ||
753        buft->iface.get_name == ggml_backend_metal_buffer_type_mapped_get_name);
754
755    GGML_UNUSED(dev);
756}
757
758static int64_t get_op_batch_size(const ggml_tensor * op) {
759    switch (op->op) {
760        case GGML_OP_MUL_MAT:
761            return op->ne[1];
762        case GGML_OP_MUL_MAT_ID:
763            return op->ne[2];
764        default:
765            return ggml_nrows(op);
766    }
767}
768
769static bool ggml_backend_metal_device_offload_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
770    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
771
772    return (op->op == GGML_OP_MUL_MAT ||
773            op->op == GGML_OP_MUL_MAT_ID) &&
774            get_op_batch_size(op) >= ggml_metal_device_get_props(ctx_dev)->op_offload_min_batch_size;
775}
776
777static ggml_backend_event_t ggml_backend_metal_device_event_new(ggml_backend_dev_t dev) {
778    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
779
780    ggml_metal_event_t event = ggml_metal_device_event_init(ctx_dev);
781    GGML_ASSERT(event);
782
783    ggml_backend_event_t ev = new ggml_backend_event {
784        /* .device  = */ dev,
785        /* .context = */ event,
786    };
787
788    return ev;
789}
790
791static void ggml_backend_metal_device_event_free(ggml_backend_dev_t dev, ggml_backend_event_t event) {
792    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
793
794    ggml_metal_event_t ev = (ggml_metal_event_t)event->context;
795
796    ggml_metal_device_event_free(ctx_dev, ev);
797
798    delete event;
799}
800
801static void ggml_backend_metal_device_event_synchronize(ggml_backend_dev_t dev, ggml_backend_event_t event) {
802    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
803
804    ggml_metal_event_t evt = (ggml_metal_event_t)event->context;
805
806    ggml_metal_device_event_synchronize(ctx_dev, evt);
807}
808
809static ggml_backend_device_i ggml_backend_metal_device_i = {
810    /* .get_name             = */ ggml_backend_metal_device_get_name,
811    /* .get_description      = */ ggml_backend_metal_device_get_description,
812    /* .get_memory           = */ ggml_backend_metal_device_get_memory,
813    /* .get_type             = */ ggml_backend_metal_device_get_type,
814    /* .get_props            = */ ggml_backend_metal_device_get_props,
815    /* .init_backend         = */ ggml_backend_metal_device_init_backend,
816    /* .get_buffer_type      = */ ggml_backend_metal_device_get_buffer_type,
817    /* .get_host_buffer_type = */ NULL,
818    /* .buffer_from_host_ptr = */ ggml_backend_metal_device_buffer_mapped,
819    /* .supports_op          = */ ggml_backend_metal_device_supports_op,
820    /* .supports_buft        = */ ggml_backend_metal_device_supports_buft,
821    /* .offload_op           = */ ggml_backend_metal_device_offload_op,
822    /* .event_new            = */ ggml_backend_metal_device_event_new,
823    /* .event_free           = */ ggml_backend_metal_device_event_free,
824    /* .event_synchronize    = */ ggml_backend_metal_device_event_synchronize,
825};
826
827// backend registry
828
829struct ggml_backend_metal_reg {
830    std::vector<ggml_backend_dev_t> devices;
831};
832
833typedef struct ggml_backend_metal_reg * ggml_backend_metal_reg_t;
834
835static ggml_backend_metal_reg_t ggml_backend_metal_reg_init(void) {
836    ggml_backend_metal_reg_t ctx = new struct ggml_backend_metal_reg;
837
838    return ctx;
839}
840
841static void ggml_backend_metal_reg_free(ggml_backend_metal_reg_t ctx) {
842    delete ctx;
843}
844
845struct ggml_backend_metal_reg_deleter {
846    void operator()(ggml_backend_metal_reg_t ctx) {
847        ggml_backend_metal_reg_free(ctx);
848    }
849};
850
851typedef std::unique_ptr<struct ggml_backend_metal_reg, ggml_backend_metal_reg_deleter> ggml_backend_metal_reg_ptr;
852
853static const char * ggml_backend_metal_reg_get_name(ggml_backend_reg_t reg) {
854    return GGML_METAL_NAME;
855
856    GGML_UNUSED(reg);
857}
858
859static size_t ggml_backend_metal_reg_device_count(ggml_backend_reg_t reg) {
860    ggml_backend_metal_reg_t ctx = (ggml_backend_metal_reg_t)reg->context;
861    return ctx->devices.size();
862}
863
864static ggml_backend_dev_t ggml_backend_metal_reg_device_get(ggml_backend_reg_t reg, size_t index) {
865    ggml_backend_metal_reg_t ctx = (ggml_backend_metal_reg_t)reg->context;
866    GGML_ASSERT(index < ctx->devices.size());
867    return ctx->devices[index];
868}
869
870static ggml_backend_feature g_ggml_backend_metal_features[] = {
871#if defined(GGML_METAL_EMBED_LIBRARY)
872    { "EMBED_LIBRARY", "1" },
873#endif
874    { NULL, NULL },
875};
876
877static ggml_backend_feature * ggml_backend_metal_get_features(ggml_backend_reg_t reg) {
878    return g_ggml_backend_metal_features;
879
880    GGML_UNUSED(reg);
881}
882
883// test/tune-only override for the FA vec (Q, NE) selection, reached via proc_address.
884static void ggml_backend_metal_tuning_set_fa_vec_override(int Q, int NE) {
885    ggml_metal_tuning::fa_vec_set_override({ (int8_t) Q, (int8_t) NE });
886}
887
888static void ggml_backend_metal_tuning_clear_fa_vec_override(void) {
889    ggml_metal_tuning::fa_vec_clear_override();
890}
891
892static int ggml_backend_metal_tuning_fa_vec_ne11_bucket(int64_t ne11) {
893    return ggml_metal_tuning::fa_vec_ne11_bucket(ne11);
894}
895
896static int ggml_backend_metal_tuning_fa_vec_ne01_bucket(int64_t ne01) {
897    return ggml_metal_tuning::fa_vec_ne01_bucket(ne01);
898}
899
900static int ggml_backend_metal_tuning_fa_vec_baseline_ne(int dk, int dv) {
901    return ggml_metal_tuning::fa_vec_baseline_ne(dk, dv);
902}
903
904static const char * ggml_backend_metal_tuning_device_token(ggml_backend_dev_t dev) {
905    ggml_metal_device_t ctx_dev = (ggml_metal_device_t)dev->context;
906
907    return ggml_metal_device_id_token(ggml_metal_device_get_props(ctx_dev)->device_id);
908}
909
910// generic fusion debugging API (ad-hoc proc-address mechanism): the test resolves the device
911// fusion context once and passes that opaque handle to the rest of the functions
912typedef void * ggml_backend_fusion_t;
913
914static ggml_backend_fusion_t ggml_backend_metal_fusion_get(ggml_backend_dev_t dev) {
915    return ggml_metal_device_get_fusion_info((ggml_metal_device_t)dev->context);
916}
917
918static void ggml_backend_metal_fusion_stats_init(ggml_backend_fusion_t finfo) {
919    ggml_metal_fusion_info_stats_init((struct ggml_metal_fusion_info *) finfo);
920}
921
922static void ggml_backend_metal_fusion_stats_reset(ggml_backend_fusion_t finfo) {
923    ggml_metal_fusion_info_stats_reset((struct ggml_metal_fusion_info *) finfo);
924}
925
926static int ggml_backend_metal_fusion_stats_get(ggml_backend_fusion_t finfo, const char ** labels, uint64_t * counts, int n) {
927    return ggml_metal_fusion_info_stats_get((struct ggml_metal_fusion_info *) finfo, labels, counts, n);
928}
929
930static void ggml_backend_metal_fusion_set_enabled(ggml_backend_fusion_t finfo, bool enabled) {
931    ggml_metal_fusion_info_set_enabled((struct ggml_metal_fusion_info *) finfo, enabled);
932}
933
934static void * ggml_backend_metal_get_proc_address(ggml_backend_reg_t reg, const char * name) {
935    if (strcmp(name, "ggml_backend_get_features") == 0) {
936        return (void *)ggml_backend_metal_get_features;
937    }
938    if (strcmp(name, "ggml_backend_metal_tuning_set_fa_vec_override") == 0) {
939        return (void *)ggml_backend_metal_tuning_set_fa_vec_override;
940    }
941    if (strcmp(name, "ggml_backend_metal_tuning_clear_fa_vec_override") == 0) {
942        return (void *)ggml_backend_metal_tuning_clear_fa_vec_override;
943    }
944    if (strcmp(name, "ggml_backend_metal_tuning_fa_vec_ne11_bucket") == 0) {
945        return (void *)ggml_backend_metal_tuning_fa_vec_ne11_bucket;
946    }
947    if (strcmp(name, "ggml_backend_metal_tuning_fa_vec_ne01_bucket") == 0) {
948        return (void *)ggml_backend_metal_tuning_fa_vec_ne01_bucket;
949    }
950    if (strcmp(name, "ggml_backend_metal_tuning_fa_vec_baseline_ne") == 0) {
951        return (void *)ggml_backend_metal_tuning_fa_vec_baseline_ne;
952    }
953    if (strcmp(name, "ggml_backend_metal_tuning_device_token") == 0) {
954        return (void *)ggml_backend_metal_tuning_device_token;
955    }
956    // generic fusion debugging API (ad-hoc proc-address mechanism, not part of the official
957    // ggml backend interface yet; a backend that adopts it exports these exact names)
958    if (strcmp(name, "ggml_backend_fusion_get") == 0) {
959        return (void *)ggml_backend_metal_fusion_get;
960    }
961    if (strcmp(name, "ggml_backend_fusion_stats_init") == 0) {
962        return (void *)ggml_backend_metal_fusion_stats_init;
963    }
964    if (strcmp(name, "ggml_backend_fusion_stats_reset") == 0) {
965        return (void *)ggml_backend_metal_fusion_stats_reset;
966    }
967    if (strcmp(name, "ggml_backend_fusion_stats_get") == 0) {
968        return (void *)ggml_backend_metal_fusion_stats_get;
969    }
970    if (strcmp(name, "ggml_backend_fusion_set_enabled") == 0) {
971        return (void *)ggml_backend_metal_fusion_set_enabled;
972    }
973
974    return NULL;
975
976    GGML_UNUSED(reg);
977}
978
979static ggml_backend_reg_i ggml_backend_metal_reg_i = {
980    /* .get_name         = */ ggml_backend_metal_reg_get_name,
981    /* .get_device_count = */ ggml_backend_metal_reg_device_count,
982    /* .get_device       = */ ggml_backend_metal_reg_device_get,
983    /* .get_proc_address = */ ggml_backend_metal_get_proc_address,
984};
985
986static ggml_backend_dev_t ggml_backend_metal_device_init(ggml_backend_reg_t reg, int device) {
987    return new ggml_backend_device {
988        /* .iface   = */ ggml_backend_metal_device_i,
989        /* .reg     = */ reg,
990        /* .context = */ ggml_metal_device_get(device, g_devices),
991    };
992}
993
994static void ggml_backend_metal_device_free(ggml_backend_dev_t dev) {
995    delete dev;
996}
997
998struct ggml_backend_device_deleter {
999    void operator()(ggml_backend_dev_t ctx) {
1000        ggml_backend_metal_device_free(ctx);
1001    }
1002};
1003
1004typedef std::unique_ptr<ggml_backend_device, ggml_backend_device_deleter> ggml_backend_device_ptr;
1005
1006ggml_backend_reg_t ggml_backend_metal_reg(void) {
1007    static ggml_backend_reg reg;
1008    static bool initialized = false;
1009
1010    {
1011        static std::mutex mutex;
1012        std::lock_guard<std::mutex> lock(mutex);
1013
1014        const char * env = getenv("GGML_METAL_DEVICES");
1015        if (env) {
1016            g_devices = atoi(env);
1017        }
1018
1019        static std::vector<ggml_backend_device_ptr> devs;
1020
1021        if (!initialized) {
1022            // workaround macOS limitation (kIOGPUCommandBufferCallbackErrorImpactingInteractivity) until proper fix becomes possible
1023            // ref: https://github.com/ggml-org/llama.cpp/issues/20141#issuecomment-4272947703
1024            setenv("AGX_RELAX_CDM_CTXSTORE_TIMEOUT", "1", true);
1025
1026            static ggml_backend_metal_reg_ptr reg_ctx(ggml_backend_metal_reg_init());
1027
1028            for (int i = 0; i < g_devices; ++i) {
1029                auto * dev = ggml_backend_metal_device_init(&reg, i);
1030                devs.emplace_back(dev);
1031
1032                reg_ctx->devices.push_back(dev);
1033            }
1034
1035            reg = {
1036                /* .api_version = */ GGML_BACKEND_API_VERSION,
1037                /* .iface       = */ ggml_backend_metal_reg_i,
1038                /* .context     = */ reg_ctx.get(),
1039            };
1040        }
1041
1042        initialized = true;
1043    }
1044
1045    return &reg;
1046}
1047
1048GGML_BACKEND_DL_IMPL(ggml_backend_metal_reg)