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
90.3 KiB2493 linesraw
1#import "ggml-metal-device.h"
2#import "ggml-metal-fusion.h"
3
4#import "ggml-impl.h"
5#import "ggml-backend-impl.h"
6#import "ggml-metal-impl.h"
7#import "ggml-metal-common.h"
8
9#include <Foundation/Foundation.h>
10
11#include <Metal/Metal.h>
12
13#include <stdatomic.h>
14
15#ifndef TARGET_OS_VISION
16#define TARGET_OS_VISION 0
17#endif
18
19// create residency sets only on macOS >= 15.0
20#if !TARGET_CPU_X86_64 && TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000 || \
21    TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 180000 || \
22    TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 180000 || \
23    TARGET_OS_VISION && __VISION_OS_VERSION_MAX_ALLOWED >= 200000
24#define GGML_METAL_HAS_RESIDENCY_SETS 1
25#endif
26
27// overload of MTLGPUFamilyMetalX (not available in some environments)
28static const NSInteger MTLGPUFamilyMetal3_GGML = 5001;
29static const NSInteger MTLGPUFamilyMetal4_GGML = 5002;
30
31// MTLLanguageVersion4_0 is not present in older SDKs
32static const NSUInteger MTLLanguageVersion4_0_GGML = 4 << 16;
33
34#if !GGML_METAL_EMBED_LIBRARY
35// Here to assist with NSBundle Path Hack
36@interface GGMLMetalClass : NSObject
37@end
38@implementation GGMLMetalClass
39@end
40#endif
41
42//
43// MTLFunctionConstantValues wrapper
44//
45
46struct ggml_metal_cv {
47    MTLFunctionConstantValues * obj;
48};
49
50ggml_metal_cv_t ggml_metal_cv_init(void) {
51    ggml_metal_cv_t res = calloc(1, sizeof(struct ggml_metal_cv));
52
53    res->obj = [[MTLFunctionConstantValues alloc] init];
54
55    return res;
56}
57
58void ggml_metal_cv_free(ggml_metal_cv_t cv) {
59    [cv->obj release];
60    free(cv);
61}
62
63void ggml_metal_cv_set_int16(ggml_metal_cv_t cv, int16_t value, int32_t idx) {
64    [cv->obj setConstantValue:&value type:MTLDataTypeShort atIndex:idx];
65}
66
67void ggml_metal_cv_set_int32(ggml_metal_cv_t cv, int32_t value, int32_t idx) {
68    [cv->obj setConstantValue:&value type:MTLDataTypeInt atIndex:idx];
69}
70
71void ggml_metal_cv_set_bool(ggml_metal_cv_t cv, bool value, int32_t idx) {
72    [cv->obj setConstantValue:&value type:MTLDataTypeBool atIndex:idx];
73}
74
75//
76// MTLComputePipelineState wrapper
77//
78
79struct ggml_metal_pipeline {
80    id<MTLComputePipelineState> obj;
81};
82
83ggml_metal_pipeline_t ggml_metal_pipeline_init(void) {
84    ggml_metal_pipeline_t res = calloc(1, sizeof(struct ggml_metal_pipeline));
85
86    *res = (struct ggml_metal_pipeline) {
87        /*.obj  =*/ nil,
88    };
89
90    return res;
91}
92
93void ggml_metal_pipeline_free(ggml_metal_pipeline_t pipeline) {
94    [pipeline->obj release];
95
96    free(pipeline);
97}
98
99int ggml_metal_pipeline_max_theads_per_threadgroup(struct ggml_metal_pipeline_with_params pipeline) {
100    return pipeline.pipeline->obj.maxTotalThreadsPerThreadgroup;
101}
102
103//
104// MTLLibrary collection (one library per op-source, compiled separately)
105//
106
107// Single source of truth for the per-kind metal libraries. The order here
108// defines the enum values and every per-kind table below, so adding a library
109// is a one-line change here (plus adding its source to CMakeLists.txt).
110//   X(suffix, name): name is both the kernels/<name>.metal basename and the
111//   ggml_metallib_<name>_{start,end} embed-symbol stem.
112#define GGML_METAL_LIBS \
113    X(FA,              fa)             \
114    X(MUL_MV,          mul_mv)         \
115    X(MUL_MM,          mul_mm)         \
116    X(QUANTIZE,        quantize)       \
117    X(SOFTMAX,         softmax)        \
118    X(NORM,            norm)           \
119    X(UNARY,           unary)          \
120    X(BINBCAST,        binbcast)       \
121    X(REDUCE,          reduce)         \
122    X(TRI,             tri)            \
123    X(SSM,             ssm)            \
124    X(WKV,             wkv)            \
125    X(GATED_DELTA_NET, gated_delta_net)\
126    X(SOLVE_TRI,       solve_tri)      \
127    X(ROPE,            rope)           \
128    X(CONV,            conv)           \
129    X(UPSCALE,         upscale)        \
130    X(ARGSORT,         argsort)        \
131    X(POOL,            pool)           \
132    X(MISC,            misc)
133
134enum ggml_metal_lib_kind {
135#define X(e, s) GGML_METAL_LIB_##e,
136    GGML_METAL_LIBS
137#undef X
138    GGML_METAL_LIB_COUNT,
139};
140
141static const char * const k_lib_names[GGML_METAL_LIB_COUNT] = {
142#define X(e, s) [GGML_METAL_LIB_##e] = #s,
143    GGML_METAL_LIBS
144#undef X
145};
146
147struct ggml_metal_library {
148    // Per-kind compiled libraries. When single_library is true, the whole library
149    // (e.g. a pre-compiled default.metallib or a from-source build) lives at
150    // objs[0] and the remaining slots are nil.
151    id<MTLLibrary> objs[GGML_METAL_LIB_COUNT];
152    bool single_library; // true: combined library at objs[0]; false: per-kind libs in objs[*]
153
154    // Routing table: kernel function name -> objs[] index, populated from each
155    // compiled library's -[MTLLibrary functionNames]. The actual compiled
156    // libraries are the single source of truth for which library owns a kernel,
157    // so adding kernels later requires no manual routing maintenance.
158    // nil in single_library mode (everything resolves to objs[0]).
159    NSMutableDictionary<NSString *, NSNumber *> * fn_to_lib;
160
161    // kernels from a second metallib, resolved ahead of the combined library
162    NSSet<NSString *> * override_fns;
163
164    ggml_metal_device_t dev;
165    ggml_metal_pipelines_t pipelines; // cache of compiled pipelines
166
167    NSLock * lock;
168};
169
170// Build the fn_to_lib routing table by querying each compiled library's public
171// function names. Call once after all per-kind libraries have been compiled.
172static void ggml_metal_library_build_index(ggml_metal_library_t lib) {
173    @autoreleasepool {
174        NSMutableDictionary<NSString *, NSNumber *> * index = [[NSMutableDictionary alloc] init];
175        for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
176            for (NSString * fname in [lib->objs[kind] functionNames]) {
177                index[fname] = @(kind);
178            }
179        }
180        lib->fn_to_lib = index;
181    }
182}
183
184// note: defined below, after struct ggml_metal_device
185static void ggml_metal_device_disable_tensor(ggml_metal_device_t dev);
186
187// the tensor API headers are exposed to the shader compiler only at Metal language version 4.0
188static void ggml_metal_compile_options_set_lang(MTLCompileOptions * options, bool has_tensor) {
189    if (!has_tensor) {
190        return;
191    }
192
193    options.languageVersion = (MTLLanguageVersion) MTLLanguageVersion4_0_GGML;
194}
195
196// Parse a `#include "name"` line. Returns the quoted name in *include_name on
197// success. Whitespace-tolerant; ignores `#include <...>` (system headers).
198static bool ggml_metal_library_parse_quoted_include(NSString * line, NSString ** include_name) {
199    NSScanner * scanner = [NSScanner scannerWithString:line];
200    scanner.charactersToBeSkipped = [NSCharacterSet whitespaceCharacterSet];
201
202    if (![scanner scanString:@"#" intoString:NULL] ||
203        ![scanner scanString:@"include" intoString:NULL] ||
204        ![scanner scanString:@"\"" intoString:NULL]) {
205        return false;
206    }
207
208    NSString * name = nil;
209    if (![scanner scanUpToString:@"\"" intoString:&name]) {
210        return false;
211    }
212
213    if (include_name) {
214        *include_name = name;
215    }
216    return true;
217}
218
219// Recursively inline `#include "name"` directives. System includes (<...>),
220// `#if/#else/#endif`, and other preprocessor lines are passed through to the
221// Metal compiler unchanged. `#pragma once` is dropped since `seen` already
222// guards against double-inclusion.
223static bool ggml_metal_library_flatten_file(NSMutableString * dst, NSString * path,
224                                            NSArray<NSString *> * search_paths,
225                                            NSMutableSet<NSString *> * seen, NSError ** error) {
226    NSString * key = [path stringByStandardizingPath];
227    if ([seen containsObject:key]) {
228        return true;
229    }
230    [seen addObject:key];
231
232    NSString * src = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:error];
233    if (!src) {
234        return false;
235    }
236
237    NSFileManager * fm = [NSFileManager defaultManager];
238    for (NSString * line in [src componentsSeparatedByString:@"\n"]) {
239        NSString * trimmed = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
240        if ([trimmed isEqualToString:@"#pragma once"]) {
241            continue;
242        }
243
244        NSString * include_name = nil;
245        if (ggml_metal_library_parse_quoted_include(line, &include_name)) {
246            NSString * resolved = nil;
247            for (NSString * dir in search_paths) {
248                NSString * candidate = [dir stringByAppendingPathComponent:include_name];
249                if ([fm isReadableFileAtPath:candidate]) {
250                    resolved = candidate;
251                    break;
252                }
253            }
254            if (!resolved) {
255                if (error) {
256                    NSString * msg = [NSString stringWithFormat:@"could not resolve include \"%@\" from '%@'", include_name, path];
257                    *error = [NSError errorWithDomain:@"ggml-metal-source-flatten" code:1
258                                             userInfo:@{NSLocalizedDescriptionKey: msg}];
259                }
260                return false;
261            }
262            if (!ggml_metal_library_flatten_file(dst, resolved, search_paths, seen, error)) {
263                return false;
264            }
265            continue;
266        }
267
268        [dst appendString:line];
269        [dst appendString:@"\n"];
270    }
271
272    return true;
273}
274
275static NSString * ggml_metal_library_flatten_source(NSString * path_source, NSError ** error) {
276    // Search paths cover both runtime layout (build/bin/kernels + build/bin)
277    // and source-tree layout (ggml/src/ggml-metal/kernels + ggml/src/ggml-metal + ggml/src).
278    NSString * path_kernels = [path_source stringByDeletingLastPathComponent];
279    NSString * path_base    = [path_kernels stringByDeletingLastPathComponent];
280    NSArray<NSString *> * search_paths = @[
281        path_kernels,
282        path_base,
283        [path_base stringByDeletingLastPathComponent],
284    ];
285
286    NSMutableString * src = [[NSMutableString alloc] init];
287    NSMutableSet<NSString *> * seen = [NSMutableSet set];
288
289    if (!ggml_metal_library_flatten_file(src, path_source, search_paths, seen, error)) {
290        [src release];
291        return nil;
292    }
293    return src;
294}
295
296// Compile all per-kind libraries in parallel. `source_for_kind` returns the MSL
297// source for a kind (the helper takes ownership and releases it), or nil with
298// *err set on failure. On success the objs[] slots are populated and the routing
299// index is built; on any failure every error is logged and false is returned
300// (the caller is responsible for freeing `res`).
301static bool ggml_metal_library_compile_all(
302        ggml_metal_library_t res,
303        id<MTLDevice> device,
304        NSDictionary * prep,
305        NSString * (^source_for_kind)(int kind, NSError ** err),
306        const char * origin) {
307    const int64_t t_start = ggml_time_us();
308
309    int64_t  * t_per_lib   = calloc(GGML_METAL_LIB_COUNT, sizeof(int64_t));
310    NSError ** err_per_lib = calloc(GGML_METAL_LIB_COUNT, sizeof(NSError *));
311    __block atomic_bool any_failure = false;
312
313    dispatch_group_t group = dispatch_group_create();
314    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
315
316    for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
317        dispatch_group_async(group, queue, ^{
318
319            const int64_t t0 = ggml_time_us();
320
321            NSError * error = nil;
322
323            NSString * src = source_for_kind(kind, &error);
324            if (!src) {
325                err_per_lib[kind] = [error retain];
326                atomic_store(&any_failure, true);
327                return;
328            }
329
330            id<MTLLibrary> lib = nil;
331
332            @autoreleasepool {
333                MTLCompileOptions * options = [MTLCompileOptions new];
334                options.preprocessorMacros = prep;
335                ggml_metal_compile_options_set_lang(options, ggml_metal_device_get_props(res->dev)->has_tensor);
336
337                lib = [device newLibraryWithSource:src options:options error:&error];
338
339                [options release];
340
341                // retain the error before the autorelease pool drains it
342                if (!lib) {
343                    err_per_lib[kind] = [error retain];
344                }
345            }
346
347            [src release];
348
349            t_per_lib[kind] = ggml_time_us() - t0;
350
351            if (!lib) {
352                atomic_store(&any_failure, true);
353                return;
354            }
355
356            res->objs[kind] = lib;
357        });
358    }
359    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
360    dispatch_release(group);
361
362    const bool ok = !atomic_load(&any_failure);
363
364    if (ok) {
365        const int64_t t_total = ggml_time_us() - t_start;
366        int64_t t_max = 0;
367        for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
368            GGML_LOG_DEBUG("%s: compiled '%s' library in %.3f sec\n",
369                           __func__, k_lib_names[kind], t_per_lib[kind] / 1e6);
370            if (t_per_lib[kind] > t_max) t_max = t_per_lib[kind];
371        }
372        GGML_LOG_INFO("%s: loaded %d libraries from %s in %.3f sec (max single = %.3f sec)\n",
373                      __func__, GGML_METAL_LIB_COUNT, origin, t_total / 1e6, t_max / 1e6);
374
375        ggml_metal_library_build_index(res);
376    } else {
377        for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
378            if (err_per_lib[kind]) {
379                GGML_LOG_ERROR("%s: failed to build '%s' library: %s\n", __func__,
380                               k_lib_names[kind], [[err_per_lib[kind] description] UTF8String]);
381                [err_per_lib[kind] release];
382            }
383        }
384    }
385
386    free(err_per_lib);
387    free(t_per_lib);
388
389    return ok;
390}
391
392// look for <name>.metallib as a bundle resource, then next to the running binary
393static NSString * ggml_metal_find_metallib(NSBundle * bundle, NSString * name) {
394    NSError * error = nil;
395
396    NSString * path_lib = [bundle pathForResource:name ofType:@"metallib"];
397    if (path_lib == nil) {
398        // Try to find the resource in the directory where the current binary located.
399        NSString * bin_cur = [[NSProcessInfo processInfo] arguments][0];
400        NSString * bin_dir = [bin_cur stringByDeletingLastPathComponent];
401
402        NSString * path_lib_default = [NSString pathWithComponents:@[bin_dir, [name stringByAppendingPathExtension:@"metallib"]]];
403        if ([[NSFileManager defaultManager] isReadableFileAtPath:path_lib_default]) {
404            GGML_LOG_INFO("%s: found '%s'\n", __func__, [path_lib_default UTF8String]);
405
406            NSDictionary * atts = [[NSFileManager defaultManager] attributesOfItemAtPath:path_lib_default error:&error];
407            if (atts && atts[NSFileType] == NSFileTypeSymbolicLink) {
408                // Optionally, if this is a symlink, try to resolve it.
409                path_lib_default = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:path_lib_default error:&error];
410                if (path_lib_default && [path_lib_default length] > 0 && ![[path_lib_default substringToIndex:1] isEqualToString:@"/"]) {
411                    // It is a relative path, adding the binary directory as directory prefix.
412                    path_lib_default = [NSString pathWithComponents:@[bin_dir, path_lib_default]];
413                }
414                if (!path_lib_default || ![[NSFileManager defaultManager] isReadableFileAtPath:path_lib_default]) {
415                    // Link to the resource could not be resolved.
416                    path_lib_default = nil;
417                } else {
418                    GGML_LOG_INFO("%s: symlink resolved '%s'\n", __func__, [path_lib_default UTF8String]);
419                }
420            }
421        } else {
422            // The resource couldn't be found in the binary's directory.
423            path_lib_default = nil;
424        }
425
426        path_lib = path_lib_default;
427    }
428
429    return path_lib;
430}
431
432ggml_metal_library_t ggml_metal_library_init(ggml_metal_device_t dev) {
433    id<MTLDevice> device = ggml_metal_device_get_obj(dev);
434
435    ggml_metal_library_t res = calloc(1, sizeof(struct ggml_metal_library));
436    res->dev       = dev;
437    res->pipelines = ggml_metal_pipelines_init();
438    res->lock      = [NSLock new];
439
440    // shared MTLCompileOptions preprocessor macros (matches the build-time defines)
441    NSMutableDictionary * prep = [NSMutableDictionary dictionary];
442    if (ggml_metal_device_get_props(dev)->has_bfloat) {
443        [prep setObject:@"1" forKey:@"GGML_METAL_HAS_BF16"];
444    }
445    if (ggml_metal_device_get_props(dev)->has_tensor) {
446        [prep setObject:@"1" forKey:@"GGML_METAL_HAS_TENSOR"];
447    }
448#if GGML_METAL_EMBED_LIBRARY
449    [prep setObject:@"1" forKey:@"GGML_METAL_EMBED_LIBRARY"];
450#endif
451
452#if GGML_METAL_EMBED_LIBRARY
453    GGML_LOG_INFO("%s: using embedded metal library\n", __func__);
454
455    // start/end symbols emitted by CMake (see CMakeLists.txt), one pair per kind
456#define X(e, s) extern const char ggml_metallib_##s##_start[]; extern const char ggml_metallib_##s##_end[];
457    GGML_METAL_LIBS
458#undef X
459
460    static const char * const lib_start[GGML_METAL_LIB_COUNT] = {
461#define X(e, s) [GGML_METAL_LIB_##e] = ggml_metallib_##s##_start,
462    GGML_METAL_LIBS
463#undef X
464    };
465    static const char * const lib_end[GGML_METAL_LIB_COUNT] = {
466#define X(e, s) [GGML_METAL_LIB_##e] = ggml_metallib_##s##_end,
467    GGML_METAL_LIBS
468#undef X
469    };
470
471    const bool ok = ggml_metal_library_compile_all(res, device, prep,
472        ^NSString * (int kind, NSError ** err) {
473            (void) err;
474            return [[NSString alloc] initWithBytes:lib_start[kind]
475                                            length:(lib_end[kind] - lib_start[kind])
476                                          encoding:NSUTF8StringEncoding];
477        }, "embedded data");
478
479    if (!ok) {
480        ggml_metal_library_free(res);
481        return NULL;
482    }
483
484    return res;
485#else
486#ifdef SWIFT_PACKAGE
487    NSBundle * bundle = SWIFTPM_MODULE_BUNDLE;
488#else
489    NSBundle * bundle = [NSBundle bundleForClass:[GGMLMetalClass class]];
490#endif
491
492    const int64_t t_start = ggml_time_us();
493
494    NSError * error = nil;
495    NSString * path_lib = ggml_metal_find_metallib(bundle, @"default");
496
497    if (path_lib != nil) {
498        // pre-compiled library found: a single combined default.metallib
499        NSURL * libURL = [NSURL fileURLWithPath:path_lib];
500        GGML_LOG_INFO("%s: loading '%s'\n", __func__, [path_lib UTF8String]);
501
502        res->objs[0]        = [device newLibraryWithURL:libURL error:&error];
503        res->single_library = true;
504        if (!res->objs[0]) {
505            GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);
506            ggml_metal_library_free(res);
507            return NULL;
508        }
509
510        // the tensor API kernels are built into a separate metallib
511        if (ggml_metal_device_get_props(dev)->has_tensor) {
512            NSString * path_mm = ggml_metal_find_metallib(bundle, @"ggml-tensor");
513
514            id<MTLLibrary> lib_mm = nil;
515            if (path_mm != nil) {
516                lib_mm = [device newLibraryWithURL:[NSURL fileURLWithPath:path_mm] error:&error];
517                if (!lib_mm && error) {
518                    GGML_LOG_ERROR("%s: %s\n", __func__, [[error description] UTF8String]);
519                }
520            }
521
522            if (lib_mm) {
523                GGML_LOG_INFO("%s: loaded '%s'\n", __func__, [path_mm UTF8String]);
524
525                res->objs[GGML_METAL_LIB_MUL_MM] = [lib_mm retain];
526                res->override_fns                = [[NSSet setWithArray:[lib_mm functionNames]] retain];
527            } else {
528                GGML_LOG_INFO("%s: ggml-tensor.metallib not found - disabling the tensor API\n", __func__);
529
530                ggml_metal_device_disable_tensor(dev);
531            }
532        }
533
534        GGML_LOG_INFO("%s: loaded in %.3f sec\n", __func__, (ggml_time_us() - t_start) / 1e6);
535        return res;
536    }
537
538    // no pre-compiled metallib: fall back to compiling each kernel source separately
539    GGML_LOG_INFO("%s: default.metallib not found, loading kernel sources\n", __func__);
540
541    NSString * path_resource = [[NSProcessInfo processInfo].environment objectForKey:@"GGML_METAL_PATH_RESOURCES"];
542    if (path_resource) {
543        GGML_LOG_INFO("%s: GGML_METAL_PATH_RESOURCES = %s\n", __func__, [path_resource UTF8String]);
544    }
545
546    // resolve each kind's source path up front (file lookup/logging stays on the calling thread)
547    NSString ** path_per_kind = calloc(GGML_METAL_LIB_COUNT, sizeof(NSString *));
548    for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
549        NSString * rel = [NSString stringWithFormat:@"kernels/%s.metal", k_lib_names[kind]];
550
551        NSString * path_source = nil;
552        if (path_resource) {
553            path_source = [path_resource stringByAppendingPathComponent:rel];
554        } else {
555            NSString * stem = [NSString stringWithFormat:@"kernels/%s", k_lib_names[kind]];
556            path_source = [bundle pathForResource:stem ofType:@"metal"];
557        }
558
559        if (path_source == nil || ![[NSFileManager defaultManager] isReadableFileAtPath:path_source]) {
560            GGML_LOG_WARN("%s: could not locate %s in bundle, falling back to cwd\n", __func__, [rel UTF8String]);
561            path_source = rel;
562        }
563
564        GGML_LOG_DEBUG("%s: loading '%s'\n", __func__, [path_source UTF8String]);
565
566        path_per_kind[kind] = [path_source retain];
567    }
568
569    const bool ok = ggml_metal_library_compile_all(res, device, prep,
570        ^NSString * (int kind, NSError ** err) {
571            return ggml_metal_library_flatten_source(path_per_kind[kind], err);
572        }, "source");
573
574    for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
575        [path_per_kind[kind] release];
576    }
577    free(path_per_kind);
578
579    if (!ok) {
580        ggml_metal_library_free(res);
581        return NULL;
582    }
583
584    return res;
585#endif
586}
587
588ggml_metal_library_t ggml_metal_library_init_from_source(ggml_metal_device_t dev, const char * source, bool verbose) {
589    if (source == NULL) {
590        GGML_LOG_ERROR("%s: source is NULL\n", __func__);
591        return NULL;
592    }
593
594    id<MTLDevice> device = ggml_metal_device_get_obj(dev);
595    id<MTLLibrary> library = nil;
596    NSError * error = nil;
597
598    const int64_t t_start = ggml_time_us();
599
600    NSString * src = [[NSString alloc] initWithBytes:source
601                                              length:strlen(source)
602                                            encoding:NSUTF8StringEncoding];
603    if (!src) {
604        GGML_LOG_ERROR("%s: failed to create NSString from source\n", __func__);
605        return NULL;
606    }
607
608    @autoreleasepool {
609        NSMutableDictionary * prep = [NSMutableDictionary dictionary];
610
611        MTLCompileOptions * options = [MTLCompileOptions new];
612        options.preprocessorMacros = prep;
613        ggml_metal_compile_options_set_lang(options, ggml_metal_device_get_props(dev)->has_tensor);
614
615        library = [device newLibraryWithSource:src options:options error:&error];
616        if (error) {
617            if (verbose) {
618                GGML_LOG_ERROR("%s: error compiling source: %s\n", __func__, [[error description] UTF8String]);
619            } else {
620                GGML_LOG_ERROR("%s: error compiling source\n", __func__);
621            }
622            library = nil;
623        }
624
625        [options release];
626    }
627
628    [src release];
629
630    if (!library) {
631        if (verbose) {
632            GGML_LOG_ERROR("%s: failed to create Metal library from source\n", __func__);
633        }
634
635        return NULL;
636    }
637
638    if (verbose) {
639        GGML_LOG_INFO("%s: compiled in %.3f sec\n", __func__, (ggml_time_us() - t_start) / 1e6);
640    }
641
642    ggml_metal_library_t res = calloc(1, sizeof(struct ggml_metal_library));
643    if (!res) {
644        GGML_LOG_ERROR("%s: calloc failed\n", __func__);
645        return NULL;
646    }
647
648    res->objs[0]        = library;
649    res->single_library = true;
650    res->dev            = dev;
651    res->pipelines      = ggml_metal_pipelines_init();
652    res->lock           = [NSLock new];
653
654    return res;
655}
656
657void ggml_metal_library_free(ggml_metal_library_t lib) {
658    if (!lib) {
659        return;
660    }
661
662    for (int kind = 0; kind < GGML_METAL_LIB_COUNT; ++kind) {
663        if (lib->objs[kind]) {
664            [lib->objs[kind] release];
665        }
666    }
667
668    if (lib->fn_to_lib) {
669        [lib->fn_to_lib release];
670    }
671
672    if (lib->override_fns) {
673        [lib->override_fns release];
674    }
675
676    ggml_metal_pipelines_free(lib->pipelines);
677
678    [lib->lock release];
679
680    free(lib);
681}
682
683ggml_metal_device_t ggml_metal_library_get_device(ggml_metal_library_t lib) {
684    return lib->dev;
685}
686
687struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline(ggml_metal_library_t lib, const char * name) {
688    [lib->lock lock];
689
690    struct ggml_metal_pipeline_with_params res = {
691        /*.pipeline =*/ nil,
692        /*.nsg      =*/ 0,
693        /*.nr0      =*/ 0,
694        /*.nr1      =*/ 0,
695        /*.smem     =*/ 0,
696        /*.c4       =*/ false,
697        /*.cnt      =*/ false,
698    };
699
700    res.pipeline = ggml_metal_pipelines_get(lib->pipelines, name);
701
702    [lib->lock unlock];
703
704    return res;
705}
706
707struct ggml_metal_pipeline_with_params ggml_metal_library_compile_pipeline(ggml_metal_library_t lib, const char * base, const char * name, ggml_metal_cv_t cv) {
708    struct ggml_metal_pipeline_with_params res = {
709        /*.pipeline =*/ nil,
710        /*.nsg      =*/ 0,
711        /*.nr0      =*/ 0,
712        /*.nr1      =*/ 0,
713        /*.smem     =*/ 0,
714        /*.c4       =*/ false,
715        /*.cnt      =*/ false,
716    };
717
718    [lib->lock lock];
719
720    res.pipeline = ggml_metal_pipelines_get(lib->pipelines, name);
721    if (res.pipeline) {
722        [lib->lock unlock];
723
724        return res;
725    }
726
727    @autoreleasepool {
728        NSError * error = nil;
729
730        NSString * base_func = [NSString stringWithUTF8String:base];
731
732        GGML_LOG_DEBUG("%s: compiling pipeline: base = '%s', name = '%s'\n", __func__, base, name);
733
734        // route to the library that actually defines this kernel; fn_to_lib is
735        // built from -[MTLLibrary functionNames] so it's always in sync
736        int lib_idx = 0;
737        if (lib->override_fns && [lib->override_fns containsObject:base_func]) {
738            lib_idx = GGML_METAL_LIB_MUL_MM;
739        } else if (!lib->single_library) {
740            NSNumber * idx = lib->fn_to_lib[base_func];
741            if (!idx) {
742                [lib->lock unlock];
743
744                GGML_LOG_ERROR("%s: kernel not found in any metal library: base = '%s', name = '%s'\n", __func__, base, name);
745
746                return res;
747            }
748            lib_idx = [idx intValue];
749        }
750
751        id<MTLLibrary> mtl_lib = lib->objs[lib_idx];
752
753        id<MTLFunction> mtl_function;
754        if (!cv) {
755            mtl_function = [mtl_lib newFunctionWithName:base_func];
756        } else {
757            mtl_function = [mtl_lib newFunctionWithName:base_func constantValues:cv->obj error:&error];
758        }
759        if (!mtl_function) {
760            [lib->lock unlock];
761
762            GGML_LOG_ERROR("%s: failed to compile pipeline: base = '%s', name = '%s'\n", __func__, base, name);
763            if (error) {
764                GGML_LOG_ERROR("%s: %s\n", __func__, [[error description] UTF8String]);
765            }
766
767            return res;
768        }
769
770        id<MTLDevice> device = ggml_metal_device_get_obj(lib->dev);
771        id<MTLComputePipelineState> obj = [device newComputePipelineStateWithFunction:mtl_function error:&error];
772
773        [mtl_function release];
774
775        if (!obj) {
776            [lib->lock unlock];
777
778            GGML_LOG_ERROR("%s: failed to create pipeline state: base = '%s', name = '%s'\n", __func__, base, name);
779            if (error) {
780                GGML_LOG_ERROR("%s: %s\n", __func__, [[error description] UTF8String]);
781            }
782
783            return res;
784        }
785
786        GGML_LOG_DEBUG("%s: loaded %-40s %16p | th_max = %4d | th_width = %4d\n", __func__, name,
787                (void *) obj,
788                (int)    obj.maxTotalThreadsPerThreadgroup,
789                (int)    obj.threadExecutionWidth);
790
791        if (obj.maxTotalThreadsPerThreadgroup == 0 || obj.threadExecutionWidth == 0) {
792            [obj release];
793
794            [lib->lock unlock];
795
796            GGML_LOG_ERROR("%s: incompatible pipeline %s\n", __func__, name);
797
798            return res;
799        }
800
801        res.pipeline = ggml_metal_pipeline_init();
802        res.pipeline->obj = obj;
803
804        ggml_metal_pipelines_add(lib->pipelines, name, res.pipeline);
805    }
806
807    [lib->lock unlock];
808
809    return res;
810}
811
812//
813// MTLComputeCommandEncoder wrapper
814//
815
816struct ggml_metal_encoder {
817    id<MTLComputeCommandEncoder> obj;
818};
819
820ggml_metal_encoder_t ggml_metal_encoder_init(ggml_metal_cmd_buf_t cmd_buf_raw, bool concurrent) {
821    ggml_metal_encoder_t res = calloc(1, sizeof(struct ggml_metal_encoder));
822
823    id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>) cmd_buf_raw;
824
825    if (concurrent) {
826        res->obj = [cmd_buf computeCommandEncoderWithDispatchType: MTLDispatchTypeConcurrent];
827    } else {
828        res->obj = [cmd_buf computeCommandEncoder];
829    }
830
831    [res->obj retain];
832
833    return res;
834}
835
836void ggml_metal_encoder_free(ggml_metal_encoder_t encoder) {
837    [encoder->obj release];
838    free(encoder);
839}
840
841void ggml_metal_encoder_debug_group_push(ggml_metal_encoder_t encoder, const char * name) {
842    @autoreleasepool {
843        [encoder->obj pushDebugGroup:[NSString stringWithCString:name encoding:NSUTF8StringEncoding]];
844    }
845}
846
847void ggml_metal_encoder_debug_group_pop (ggml_metal_encoder_t encoder) {
848    [encoder->obj popDebugGroup];
849}
850
851void ggml_metal_encoder_set_pipeline(ggml_metal_encoder_t encoder, struct ggml_metal_pipeline_with_params pipeline) {
852    if (!pipeline.pipeline) {
853        GGML_ABORT("%s: nil Metal pipeline (missing kernel; see compile_pipeline log above)\n", __func__);
854    }
855
856    [encoder->obj setComputePipelineState:pipeline.pipeline->obj];
857}
858
859void ggml_metal_encoder_set_bytes(ggml_metal_encoder_t encoder, void * data, size_t size, int idx) {
860    [encoder->obj setBytes:data length:size atIndex:idx];
861}
862
863void ggml_metal_encoder_set_buffer(ggml_metal_encoder_t encoder, struct ggml_metal_buffer_id buffer, int idx) {
864    [encoder->obj setBuffer:buffer.metal offset:buffer.offs atIndex:idx];
865}
866
867void ggml_metal_encoder_set_threadgroup_memory_size(ggml_metal_encoder_t encoder, size_t size, int idx) {
868    // ref: https://developer.apple.com/documentation/metal/mtlcomputecommandencoder/setthreadgroupmemorylength(_:index:)
869    GGML_ASSERT(size % 16 == 0);
870
871    [encoder->obj setThreadgroupMemoryLength:size atIndex:idx];
872}
873
874void ggml_metal_encoder_dispatch_threadgroups(ggml_metal_encoder_t encoder, int tg0, int tg1, int tg2, int tptg0, int tptg1, int tptg2) {
875    [encoder->obj dispatchThreadgroups:MTLSizeMake(tg0, tg1, tg2) threadsPerThreadgroup:MTLSizeMake(tptg0, tptg1, tptg2)];
876}
877
878void ggml_metal_encoder_memory_barrier(ggml_metal_encoder_t encoder) {
879    [encoder->obj memoryBarrierWithScope:MTLBarrierScopeBuffers];
880}
881
882void ggml_metal_encoder_end_encoding(ggml_metal_encoder_t encoder) {
883    [encoder->obj endEncoding];
884}
885
886struct ggml_metal_device {
887    id<MTLDevice> mtl_device;
888
889    // a single global queue shared by all Metal backends
890    // technically not needed for devices with unified memory, but enables discrete GPUs support
891    // ref: https://github.com/ggml-org/llama.cpp/pull/15906
892    id<MTLCommandQueue> mtl_queue;
893
894    ggml_metal_rsets_t rsets;
895
896    ggml_metal_library_t library;
897
898    struct ggml_metal_device_props props;
899
900    // shared fusion debugging context
901    struct ggml_metal_fusion_info * finfo;
902
903    // virtual address for GPU memory allocations
904    atomic_uintptr_t addr_virt;
905};
906
907//
908// MTLResidenceSet wrapper
909//
910
911struct ggml_metal_rsets {
912    NSLock * lock;
913
914    NSMutableArray * data;
915
916    // number of seconds since the last graph computation
917    // keep the residency sets wired for that amount of time to avoid being collected by the OS
918    int keep_alive_s;
919    int loops_per_s;
920    int time_per_loop_ms;
921
922    // background heartbeat thread to keep the residency sets alive
923    atomic_bool d_stop;
924    atomic_int  d_loop;
925
926    dispatch_group_t d_group;
927};
928
929#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
930static void ggml_metal_dummy_work(ggml_metal_device_t dev) {
931    if (dev->mtl_queue == nil) {
932        return;
933    }
934
935    @autoreleasepool {
936        // perform a minimal dummy operation on the GPU
937        id<MTLBuffer> buf = [dev->mtl_device newBufferWithLength:1 options:MTLResourceStorageModePrivate];
938        id<MTLCommandBuffer> cmd_buf = [dev->mtl_queue commandBuffer];
939
940        {
941            id<MTLBlitCommandEncoder> encoder = [cmd_buf blitCommandEncoder];
942
943            [encoder fillBuffer:buf range:NSMakeRange(0, 1) value:0];
944
945            [encoder endEncoding];
946        }
947
948        [cmd_buf commit];
949        [buf release];
950    }
951}
952#endif
953
954ggml_metal_rsets_t ggml_metal_rsets_init(ggml_metal_device_t dev) {
955    ggml_metal_rsets_t res = calloc(1, sizeof(struct ggml_metal_rsets));
956
957    res->lock = [[NSLock alloc] init];
958    res->data = [[NSMutableArray alloc] init];
959
960    // by default keep the memory wired for 3 minutes
961    res->keep_alive_s = 3*60;
962
963    const char * GGML_METAL_RESIDENCY_KEEP_ALIVE_S = getenv("GGML_METAL_RESIDENCY_KEEP_ALIVE_S");
964    if (GGML_METAL_RESIDENCY_KEEP_ALIVE_S) {
965        res->keep_alive_s = atoi(GGML_METAL_RESIDENCY_KEEP_ALIVE_S);
966    }
967
968    if (res->keep_alive_s <= 0) {
969        res->keep_alive_s = 3*60;
970    }
971
972    res->time_per_loop_ms = 5;
973    res->loops_per_s = 1000/res->time_per_loop_ms;
974
975    GGML_LOG_INFO("%s: creating a residency set collection (keep_alive = %d s)\n", __func__, res->keep_alive_s);
976
977    atomic_store_explicit(&res->d_stop, false, memory_order_relaxed);
978    atomic_store_explicit(&res->d_loop, res->loops_per_s*res->keep_alive_s, memory_order_relaxed);
979
980    res->d_group = dispatch_group_create();
981
982    // start a background thread that periodically requests residency for all the currently active sets in the collection
983    // the requests stop after a certain amount of time (keep_alive_s) of inactivity
984    dispatch_queue_t d_queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
985    dispatch_group_async(res->d_group, d_queue, ^{
986#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
987        if (@available(macOS 15.0, iOS 18.0, tvOS 18.0, visionOS 2.0, *)) {
988              while (!atomic_load_explicit(&res->d_stop, memory_order_relaxed)) {
989                  if (atomic_load_explicit(&res->d_loop, memory_order_relaxed) > 0) {
990                      [res->lock lock];
991
992                      for (int i = 0; i < (int) res->data.count; ++i) {
993                          [res->data[i] requestResidency];
994                      }
995
996                      atomic_fetch_sub_explicit(&res->d_loop, 1, memory_order_relaxed);
997
998                      [res->lock unlock];
999                  }
1000
1001                  usleep(res->time_per_loop_ms * 1000);
1002              }
1003        }
1004#endif
1005    });
1006
1007#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
1008    if (@available(macOS 15.0, iOS 18.0, tvOS 18.0, visionOS 2.0, *)) {
1009        // workaround for residency set memory not being released if no GPU operation occurs
1010        // https://developer.apple.com/forums/thread/839089
1011        // https://github.com/ggml-org/llama.cpp/issues/25937
1012        ggml_metal_dummy_work(dev);
1013    }
1014#endif
1015
1016    return res;
1017}
1018
1019void ggml_metal_rsets_free(ggml_metal_rsets_t rsets) {
1020    if (rsets == NULL) {
1021        return;
1022    }
1023
1024    // note: if you hit this assert, most likely you haven't deallocated all Metal resources before exiting
1025    GGML_ASSERT([rsets->data count] == 0);
1026
1027    atomic_store_explicit(&rsets->d_stop, true, memory_order_relaxed);
1028
1029    dispatch_group_wait(rsets->d_group, DISPATCH_TIME_FOREVER);
1030    dispatch_release(rsets->d_group);
1031
1032    [rsets->data release];
1033    [rsets->lock release];
1034
1035    free(rsets);
1036}
1037
1038static const struct {
1039    const char *              name;
1040    const char *              token;
1041    enum ggml_metal_device_id id;
1042} k_metal_devices[] = {
1043#define DEV(name, id) { name, #id, id }
1044    DEV("M1",       GGML_METAL_DEVICE_M1),
1045    DEV("M1 Pro",   GGML_METAL_DEVICE_M1_PRO),
1046    DEV("M1 Max",   GGML_METAL_DEVICE_M1_MAX),
1047    DEV("M1 Ultra", GGML_METAL_DEVICE_M1_ULTRA),
1048    DEV("M2",       GGML_METAL_DEVICE_M2),
1049    DEV("M2 Pro",   GGML_METAL_DEVICE_M2_PRO),
1050    DEV("M2 Max",   GGML_METAL_DEVICE_M2_MAX),
1051    DEV("M2 Ultra", GGML_METAL_DEVICE_M2_ULTRA),
1052    DEV("M3",       GGML_METAL_DEVICE_M3),
1053    DEV("M3 Pro",   GGML_METAL_DEVICE_M3_PRO),
1054    DEV("M3 Max",   GGML_METAL_DEVICE_M3_MAX),
1055    DEV("M3 Ultra", GGML_METAL_DEVICE_M3_ULTRA),
1056    DEV("M4",       GGML_METAL_DEVICE_M4),
1057    DEV("M4 Pro",   GGML_METAL_DEVICE_M4_PRO),
1058    DEV("M4 Max",   GGML_METAL_DEVICE_M4_MAX),
1059    DEV("M5",       GGML_METAL_DEVICE_M5),
1060    DEV("M5 Pro",   GGML_METAL_DEVICE_M5_PRO),
1061    DEV("M5 Max",   GGML_METAL_DEVICE_M5_MAX),
1062    DEV("M5 Ultra", GGML_METAL_DEVICE_M5_ULTRA),
1063    DEV("A18 Pro",  GGML_METAL_DEVICE_A18_PRO),
1064#undef DEV
1065};
1066
1067static enum ggml_metal_device_id ggml_metal_device_id_parse(const char * name) {
1068    if (!name) {
1069        return GGML_METAL_DEVICE_GENERIC;
1070    }
1071
1072    static const char prefix[] = "Apple ";
1073    if (strncmp(name, prefix, sizeof(prefix) - 1) != 0) {
1074        return GGML_METAL_DEVICE_GENERIC;
1075    }
1076    const char * suffix = name + sizeof(prefix) - 1;
1077
1078    for (size_t i = 0; i < sizeof(k_metal_devices)/sizeof(k_metal_devices[0]); ++i) {
1079        if (strcmp(suffix, k_metal_devices[i].name) == 0) {
1080            return k_metal_devices[i].id;
1081        }
1082    }
1083    return GGML_METAL_DEVICE_GENERIC;
1084}
1085
1086const char * ggml_metal_device_id_token(enum ggml_metal_device_id id) {
1087    for (size_t i = 0; i < sizeof(k_metal_devices)/sizeof(k_metal_devices[0]); ++i) {
1088        if (k_metal_devices[i].id == id) {
1089            return k_metal_devices[i].token;
1090        }
1091    }
1092    return "GGML_METAL_DEVICE_GENERIC";
1093}
1094
1095ggml_metal_device_t ggml_metal_device_init(int device, int n_devices) {
1096    ggml_metal_device_t dev = calloc(1, sizeof(struct ggml_metal_device));
1097
1098    assert(dev != NULL);
1099
1100    @autoreleasepool {
1101        if (dev->mtl_device == nil) {
1102            dev->mtl_device = MTLCreateSystemDefaultDevice();
1103
1104            if (dev->mtl_device) {
1105                dev->mtl_queue = [dev->mtl_device newCommandQueue];
1106                if (dev->mtl_queue == nil) {
1107                    GGML_LOG_ERROR("%s: error: failed to create command queue\n", __func__);
1108                }
1109
1110                dev->addr_virt = 0x000000400ULL;
1111
1112                dev->props.device = device;
1113
1114                // the Metal backend uses the system default device as the single physical device;
1115                // additional (virtual) devices are emulated on top of it via GGML_METAL_DEVICES
1116                dev->props.device_phys = 0;
1117                dev->props.device_virt = device;
1118
1119                dev->props.has_simdgroup_reduction  = [dev->mtl_device supportsFamily:MTLGPUFamilyApple7];
1120                dev->props.has_simdgroup_reduction |= [dev->mtl_device supportsFamily:MTLGPUFamilyMetal3_GGML];
1121
1122                dev->props.has_simdgroup_mm = [dev->mtl_device supportsFamily:MTLGPUFamilyApple7];
1123                dev->props.has_unified_memory = dev->mtl_device.hasUnifiedMemory;
1124
1125                dev->props.has_bfloat  = [dev->mtl_device supportsFamily:MTLGPUFamilyMetal3_GGML];
1126                dev->props.has_bfloat |= [dev->mtl_device supportsFamily:MTLGPUFamilyApple6];
1127                if (getenv("GGML_METAL_BF16_DISABLE") != NULL) {
1128                    dev->props.has_bfloat = false;
1129                }
1130
1131                dev->props.has_tensor = [dev->mtl_device supportsFamily:MTLGPUFamilyMetal4_GGML];
1132                if (getenv("GGML_METAL_TENSOR_DISABLE") != NULL) {
1133                    dev->props.has_tensor = false;
1134                }
1135
1136                // note: disable the tensor API by default for old chips because with the current implementation it is not useful
1137                // - M2 Ultra:   ~5% slower
1138                // - M4, M4 Max: no significant difference
1139                //
1140                // TODO: try to update the tensor API kernels to at least match the simdgroup performance
1141                if (getenv("GGML_METAL_TENSOR_ENABLE") == NULL &&
1142                    ![[dev->mtl_device name] containsString:@"M5"] &&
1143                    ![[dev->mtl_device name] containsString:@"M6"] &&
1144                    ![[dev->mtl_device name] containsString:@"A19"] &&
1145                    ![[dev->mtl_device name] containsString:@"A20"]) {
1146                    GGML_LOG_INFO("%s: tensor API disabled for pre-M5 and pre-A19 devices\n", __func__);
1147                    dev->props.has_tensor = false;
1148                }
1149
1150                // double-check that the tensor API compiles
1151                if (dev->props.has_tensor) {
1152                    const char * src_tensor_f16 = "\n"
1153                        "#include <metal_stdlib> \n"
1154                        "#include <metal_tensor> \n"
1155                        "#include <MetalPerformancePrimitives/MetalPerformancePrimitives.h> \n"
1156                        " \n"
1157                        "using namespace metal; \n"
1158                        "using namespace mpp::tensor_ops; \n"
1159                        " \n"
1160                        "kernel void dummy_kernel( \n"
1161                        "    tensor<device  half, dextents<int32_t, 2>> A [[buffer(0)]], \n"
1162                        "    tensor<device  half, dextents<int32_t, 2>> B [[buffer(1)]], \n"
1163                        "    device float * C [[buffer(2)]], \n"
1164                        "    uint2 tgid [[threadgroup_position_in_grid]]) \n"
1165                        "{ \n"
1166                        "    auto tA = A.slice(0, (int)tgid.y); \n"
1167                        "    auto tB = B.slice((int)tgid.x, 0); \n"
1168                        " \n"
1169                        "    matmul2d< \n"
1170                        "        matmul2d_descriptor(16, 16, dynamic_extent), \n"
1171                        "        execution_simdgroups<4>> mm; \n"
1172                        " \n"
1173                        "    auto cT = mm.get_destination_cooperative_tensor<decltype(tA), decltype(tB), float>(); \n"
1174                        " \n"
1175                        "    auto sA = tA.slice(0, 0); \n"
1176                        "    auto sB = tB.slice(0, 0); \n"
1177                        "    mm.run(sB, sA, cT); \n"
1178                        " \n"
1179                        "    auto tC = tensor<device float, dextents<int32_t, 2>, tensor_inline>(C, dextents<int32_t, 2>(16, 16)); \n"
1180                        " \n"
1181                        "    cT.store(tC); \n"
1182                        "}";
1183
1184                    GGML_LOG_INFO("%s: testing tensor API for f16 support\n", __func__);
1185                    ggml_metal_library_t lib = ggml_metal_library_init_from_source(dev, src_tensor_f16, false);
1186                    if (lib == NULL) {
1187                        GGML_LOG_WARN("%s: - the tensor API is not supported in this environment - disabling\n", __func__);
1188                        dev->props.has_tensor = false;
1189                    } else {
1190                        struct ggml_metal_pipeline_with_params ppl = ggml_metal_library_compile_pipeline(lib, "dummy_kernel", "dummy_kernel", nil);
1191                        if (!ppl.pipeline) {
1192                            GGML_LOG_WARN("%s: - the tensor API is not supported in this environment - disabling\n", __func__);
1193                            dev->props.has_tensor = false;
1194                        }
1195
1196                        ggml_metal_library_free(lib);
1197                    }
1198                }
1199
1200                // try to compile a dummy kernel to determine if the tensor API is supported for bfloat
1201                if (dev->props.has_tensor && dev->props.has_bfloat) {
1202                    const char * src_tensor_bf16 = "\n"
1203                        "#include <metal_stdlib> \n"
1204                        "#include <metal_tensor> \n"
1205                        "#include <MetalPerformancePrimitives/MetalPerformancePrimitives.h> \n"
1206                        " \n"
1207                        "using namespace metal; \n"
1208                        "using namespace mpp::tensor_ops; \n"
1209                        " \n"
1210                        "kernel void dummy_kernel( \n"
1211                        "    tensor<device bfloat, dextents<int32_t, 2>> A [[buffer(0)]], \n"
1212                        "    tensor<device bfloat, dextents<int32_t, 2>> B [[buffer(1)]], \n"
1213                        "    device float * C [[buffer(2)]], \n"
1214                        "    uint2 tgid [[threadgroup_position_in_grid]]) \n"
1215                        "{ \n"
1216                        "    auto tA = A.slice(0, (int)tgid.y); \n"
1217                        "    auto tB = B.slice((int)tgid.x, 0); \n"
1218                        " \n"
1219                        "    matmul2d< \n"
1220                        "        matmul2d_descriptor(16, 16, dynamic_extent), \n"
1221                        "        execution_simdgroups<4>> mm; \n"
1222                        " \n"
1223                        "    auto cT = mm.get_destination_cooperative_tensor<decltype(tA), decltype(tB), float>(); \n"
1224                        " \n"
1225                        "    auto sA = tA.slice(0, 0); \n"
1226                        "    auto sB = tB.slice(0, 0); \n"
1227                        "    mm.run(sB, sA, cT); \n"
1228                        " \n"
1229                        "    auto tC = tensor<device float, dextents<int32_t, 2>, tensor_inline>(C, dextents<int32_t, 2>(16, 16)); \n"
1230                        " \n"
1231                        "    cT.store(tC); \n"
1232                        "}";
1233
1234                    GGML_LOG_INFO("%s: testing tensor API for bfloat support\n", __func__);
1235                    ggml_metal_library_t lib = ggml_metal_library_init_from_source(dev, src_tensor_bf16, false);
1236                    if (lib == NULL) {
1237                        GGML_LOG_WARN("%s: - the tensor API does not support bfloat - disabling bfloat support\n", __func__);
1238                        dev->props.has_bfloat = false;
1239                    } else {
1240                        struct ggml_metal_pipeline_with_params ppl = ggml_metal_library_compile_pipeline(lib, "dummy_kernel", "dummy_kernel", nil);
1241                        if (!ppl.pipeline) {
1242                            GGML_LOG_WARN("%s: - the tensor API does not support bfloat - disabling bfloat support\n", __func__);
1243                            dev->props.has_bfloat = false;
1244                        }
1245
1246                        ggml_metal_library_free(lib);
1247                    }
1248                }
1249
1250                dev->props.use_residency_sets = true;
1251#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
1252                dev->props.use_residency_sets = getenv("GGML_METAL_NO_RESIDENCY") == nil;
1253#endif
1254
1255                dev->props.use_shared_buffers = dev->props.has_unified_memory;
1256#if TARGET_OS_OSX
1257                // In case of eGPU, shared memory may be preferable.
1258                dev->props.use_shared_buffers |= [dev->mtl_device location] == MTLDeviceLocationExternal;
1259#endif
1260                if (getenv("GGML_METAL_SHARED_BUFFERS_DISABLE") != NULL) {
1261                    dev->props.use_shared_buffers = false;
1262                }
1263                if (getenv("GGML_METAL_SHARED_BUFFERS_ENABLE") != NULL) {
1264                    dev->props.use_shared_buffers = true;
1265                }
1266
1267                dev->props.supports_gpu_family_apple7 = [dev->mtl_device supportsFamily:MTLGPUFamilyApple7];
1268
1269                dev->props.device_id = ggml_metal_device_id_parse([[dev->mtl_device name] UTF8String]);
1270
1271                dev->props.op_offload_min_batch_size  = getenv("GGML_OP_OFFLOAD_MIN_BATCH") ? atoi(getenv("GGML_OP_OFFLOAD_MIN_BATCH")) : 32;
1272
1273                dev->props.max_buffer_size            = dev->mtl_device.maxBufferLength;
1274                dev->props.max_theadgroup_memory_size = dev->mtl_device.maxThreadgroupMemoryLength;
1275                if (@available(macOS 10.12, iOS 16.0, *)) {
1276                    dev->props.max_working_set_size   = dev->mtl_device.recommendedMaxWorkingSetSize;
1277                } else {
1278                    dev->props.max_working_set_size   = dev->mtl_device.maxBufferLength;
1279                }
1280
1281                {
1282                    const char * val = getenv("GGML_METAL_FUSION_DEBUG");
1283                    dev->finfo = ggml_metal_fusion_info_init(
1284                            getenv("GGML_METAL_FUSION_DISABLE") == nil,
1285                            val ? atoi(val) : 0);
1286                }
1287
1288                snprintf(dev->props.name, sizeof(dev->props.name), "%s%d", "MTL", device);
1289                const char * gpu_name = [[dev->mtl_device name] UTF8String];
1290                if (n_devices > 1) {
1291                    snprintf(dev->props.desc, sizeof(dev->props.desc), "%s (dev p%d/v%d)",
1292                             gpu_name, dev->props.device_phys, dev->props.device_virt);
1293                } else {
1294                    snprintf(dev->props.desc, sizeof(dev->props.desc), "%s", gpu_name);
1295                }
1296
1297                dev->library = ggml_metal_library_init(dev);
1298                if (!dev->library) {
1299                    GGML_LOG_ERROR("%s: error: failed to create library\n", __func__);
1300                }
1301
1302                if (dev->props.use_residency_sets) {
1303                    dev->rsets = ggml_metal_rsets_init(dev);
1304                } else {
1305                    dev->rsets = nil;
1306                }
1307
1308                // print MTL GPU family:
1309                GGML_LOG_INFO("%s: GPU name:   %s (%s)\n", __func__, dev->props.name, dev->props.desc);
1310
1311                // determine max supported GPU family
1312                // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
1313                // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
1314                {
1315                    for (int i = MTLGPUFamilyApple1 + 20; i >= MTLGPUFamilyApple1; --i) {
1316                        if ([dev->mtl_device supportsFamily:i]) {
1317                            dev->props.gpu_family = i - (int) MTLGPUFamilyApple1 + 1;
1318                            GGML_LOG_INFO("%s: GPU family: MTLGPUFamilyApple%d  (%d)\n", __func__, dev->props.gpu_family, i);
1319                            break;
1320                        }
1321                    }
1322
1323                    for (int i = MTLGPUFamilyCommon1 + 5; i >= MTLGPUFamilyCommon1; --i) {
1324                        if ([dev->mtl_device supportsFamily:i]) {
1325                            GGML_LOG_INFO("%s: GPU family: MTLGPUFamilyCommon%d (%d)\n", __func__, i - (int) MTLGPUFamilyCommon1 + 1, i);
1326                            break;
1327                        }
1328                    }
1329
1330                    for (int i = MTLGPUFamilyMetal3_GGML + 5; i >= MTLGPUFamilyMetal3_GGML; --i) {
1331                        if ([dev->mtl_device supportsFamily:i]) {
1332                            GGML_LOG_INFO("%s: GPU family: MTLGPUFamilyMetal%d  (%d)\n", __func__, i - (int) MTLGPUFamilyMetal3_GGML + 3, i);
1333                            break;
1334                        }
1335                    }
1336                }
1337
1338                GGML_LOG_INFO("%s: simdgroup reduction   = %s\n", __func__, dev->props.has_simdgroup_reduction ? "true" : "false");
1339                GGML_LOG_INFO("%s: simdgroup matrix mul. = %s\n", __func__, dev->props.has_simdgroup_mm        ? "true" : "false");
1340                GGML_LOG_INFO("%s: has unified memory    = %s\n", __func__, dev->props.has_unified_memory      ? "true" : "false");
1341                GGML_LOG_INFO("%s: has bfloat            = %s\n", __func__, dev->props.has_bfloat              ? "true" : "false");
1342                GGML_LOG_INFO("%s: has tensor            = %s\n", __func__, dev->props.has_tensor              ? "true" : "false");
1343                GGML_LOG_INFO("%s: use residency sets    = %s\n", __func__, dev->props.use_residency_sets      ? "true" : "false");
1344                GGML_LOG_INFO("%s: use shared buffers    = %s\n", __func__, dev->props.use_shared_buffers      ? "true" : "false");
1345
1346#if TARGET_OS_OSX || (TARGET_OS_IOS && __clang_major__ >= 15)
1347                if (@available(macOS 10.12, iOS 16.0, *)) {
1348                    GGML_LOG_INFO("%s: recommendedMaxWorkingSetSize  = %8.2f MB\n", __func__, dev->props.max_working_set_size / 1e6);
1349                }
1350#endif
1351            }
1352        }
1353    }
1354
1355    return dev;
1356}
1357
1358void ggml_metal_device_free(ggml_metal_device_t dev) {
1359    assert(dev != NULL);
1360
1361    @autoreleasepool {
1362        ggml_metal_fusion_info_free(dev->finfo);
1363
1364        ggml_metal_rsets_free(dev->rsets);
1365
1366        ggml_metal_library_free(dev->library);
1367        dev->library = NULL;
1368
1369        if (dev->mtl_queue) {
1370            [dev->mtl_queue release];
1371            dev->mtl_queue = nil;
1372        }
1373
1374        if (dev->mtl_device) {
1375            [dev->mtl_device release];
1376            dev->mtl_device = nil;
1377        }
1378    }
1379
1380    free(dev);
1381}
1382
1383void * ggml_metal_device_get_obj(ggml_metal_device_t dev) {
1384    return dev->mtl_device;
1385}
1386
1387void * ggml_metal_device_get_queue(ggml_metal_device_t dev) {
1388    return dev->mtl_queue;
1389}
1390
1391ggml_metal_library_t ggml_metal_device_get_library(ggml_metal_device_t dev) {
1392    return dev->library;
1393}
1394
1395void ggml_metal_device_rsets_add(ggml_metal_device_t dev, ggml_metal_rset_t rset) {
1396    if (rset == nil) {
1397        return;
1398    }
1399
1400    GGML_ASSERT(dev->rsets);
1401
1402    [dev->rsets->lock lock];
1403
1404    [dev->rsets->data addObject:rset];
1405
1406    [dev->rsets->lock unlock];
1407}
1408
1409void ggml_metal_device_rsets_rm(ggml_metal_device_t dev, ggml_metal_rset_t rset) {
1410    if (rset == nil) {
1411        return;
1412    }
1413
1414    GGML_ASSERT(dev->rsets);
1415
1416    [dev->rsets->lock lock];
1417
1418    [dev->rsets->data removeObject:rset];
1419
1420    [dev->rsets->lock unlock];
1421}
1422
1423void ggml_metal_device_rsets_keep_alive(ggml_metal_device_t dev) {
1424    if (dev->rsets == NULL) {
1425        return;
1426    }
1427
1428    atomic_store_explicit(&dev->rsets->d_loop, dev->rsets->loops_per_s*dev->rsets->keep_alive_s, memory_order_relaxed);
1429}
1430
1431struct ggml_metal_event {
1432    void * obj; // id<MTLSharedEvent>
1433
1434    atomic_int value;
1435};
1436
1437void ggml_metal_event_encode_signal(ggml_metal_event_t ev, ggml_metal_cmd_buf_t cmd_buf_raw) {
1438    id<MTLSharedEvent> event = (id<MTLSharedEvent>)ev->obj;
1439
1440    id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>) cmd_buf_raw;
1441
1442    [cmd_buf encodeSignalEvent:event value:atomic_fetch_add_explicit(&ev->value, 1, memory_order_relaxed) + 1];
1443}
1444
1445void ggml_metal_event_encode_wait(ggml_metal_event_t ev, ggml_metal_cmd_buf_t cmd_buf_raw) {
1446    id<MTLSharedEvent> event = (id<MTLSharedEvent>)ev->obj;
1447
1448    id<MTLCommandBuffer> cmd_buf = (id<MTLCommandBuffer>) cmd_buf_raw;
1449
1450    [cmd_buf encodeWaitForEvent:event value:atomic_load_explicit(&ev->value, memory_order_relaxed)];
1451}
1452
1453ggml_metal_event_t ggml_metal_device_event_init(ggml_metal_device_t dev) {
1454    id<MTLSharedEvent> event = [dev->mtl_device newSharedEvent];
1455
1456    ggml_metal_event_t ev = calloc(1, sizeof(struct ggml_metal_event));
1457
1458    ev->obj = (__bridge void *)event;
1459    ev->value = 0;
1460
1461    return ev;
1462}
1463
1464void ggml_metal_device_event_free(ggml_metal_device_t dev, ggml_metal_event_t ev) {
1465    @autoreleasepool {
1466        id<MTLSharedEvent> event = ev->obj;
1467        [event release];
1468
1469        free(ev);
1470
1471        GGML_UNUSED(dev);
1472    }
1473}
1474
1475void ggml_metal_device_event_synchronize(ggml_metal_device_t dev, ggml_metal_event_t ev) {
1476    id<MTLSharedEvent> event = ev->obj;
1477    const bool res = [event waitUntilSignaledValue:atomic_load_explicit(&ev->value, memory_order_relaxed) timeoutMS:60000];
1478    if (!res) {
1479        GGML_ABORT("%s: failed to wait for event\n", __func__);
1480    }
1481
1482    GGML_UNUSED(dev);
1483}
1484
1485void ggml_metal_device_get_memory(ggml_metal_device_t dev, size_t * free, size_t * total) {
1486    if (@available(macOS 10.12, iOS 16.0, *)) {
1487        *total     = dev->mtl_device.recommendedMaxWorkingSetSize;
1488        size_t cur = dev->mtl_device.currentAllocatedSize;
1489        // it's possible to allocate more than `recommendedMaxWorkingSetSize`
1490        *free      = *total > cur ? *total - cur : 0;
1491    } else {
1492        *free = 0;
1493        *total = 0;
1494    }
1495}
1496
1497static bool ggml_metal_supports_mul_mat_op(
1498        bool has_simdgroup_reduction,
1499        const struct ggml_tensor * op,
1500        bool src0_f16_has_mv,
1501        bool mm_path) {
1502    if (!has_simdgroup_reduction ||
1503        op->src[0]->type == GGML_TYPE_NVFP4 ||
1504        op->src[0]->type == GGML_TYPE_TQ1_0) {
1505        return false;
1506    }
1507
1508    if (op->src[1]->type != GGML_TYPE_F16) {
1509        return true;
1510    }
1511
1512    if (op->src[0]->type == GGML_TYPE_BF16) {
1513        return false;
1514    }
1515
1516    if (src0_f16_has_mv && op->src[0]->type == GGML_TYPE_F16) {
1517        return true;
1518    }
1519
1520    return mm_path;
1521}
1522
1523bool ggml_metal_device_supports_op(ggml_metal_device_t dev, const struct ggml_tensor * op) {
1524    const bool has_simdgroup_mm        = dev->props.has_simdgroup_mm;
1525    const bool has_simdgroup_reduction = dev->props.has_simdgroup_reduction;
1526    const bool has_bfloat              = dev->props.has_bfloat;
1527
1528    if (!has_bfloat) {
1529        if (op->type == GGML_TYPE_BF16) {
1530            return false;
1531        }
1532
1533        for (size_t i = 0, n = 3; i < n; ++i) {
1534            if (op->src[i] != NULL && op->src[i]->type == GGML_TYPE_BF16) {
1535                return false;
1536            }
1537        }
1538    }
1539
1540    switch (op->op) {
1541        case GGML_OP_SCALE:
1542        case GGML_OP_FILL:
1543        case GGML_OP_CLAMP:
1544        case GGML_OP_SQR:
1545        case GGML_OP_SQRT:
1546        case GGML_OP_SIN:
1547        case GGML_OP_COS:
1548        case GGML_OP_LOG:
1549            return ggml_is_contiguous_rows(op->src[0]) && (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16);
1550        case GGML_OP_UNARY:
1551            switch (ggml_get_unary_op(op)) {
1552                case GGML_UNARY_OP_TANH:
1553                case GGML_UNARY_OP_RELU:
1554                case GGML_UNARY_OP_SIGMOID:
1555                case GGML_UNARY_OP_GELU:
1556                case GGML_UNARY_OP_GELU_ERF:
1557                case GGML_UNARY_OP_GELU_QUICK:
1558                case GGML_UNARY_OP_SILU:
1559                case GGML_UNARY_OP_ELU:
1560                case GGML_UNARY_OP_NEG:
1561                case GGML_UNARY_OP_ABS:
1562                case GGML_UNARY_OP_SGN:
1563                case GGML_UNARY_OP_STEP:
1564                case GGML_UNARY_OP_HARDSWISH:
1565                case GGML_UNARY_OP_HARDSIGMOID:
1566                case GGML_UNARY_OP_EXP:
1567                case GGML_UNARY_OP_SOFTPLUS:
1568                case GGML_UNARY_OP_EXPM1:
1569                case GGML_UNARY_OP_FLOOR:
1570                case GGML_UNARY_OP_CEIL:
1571                case GGML_UNARY_OP_ROUND:
1572                case GGML_UNARY_OP_TRUNC:
1573                case GGML_UNARY_OP_XIELU:
1574                    return ggml_is_contiguous_rows(op->src[0]) && (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16);
1575                default:
1576                    return false;
1577            }
1578        case GGML_OP_SILU_BACK:
1579            return (op->src[0]->type == GGML_TYPE_F32) &&
1580                (op->src[1]->type == GGML_TYPE_F32) &&
1581                (op->type == GGML_TYPE_F32) &&
1582                ggml_is_contiguous(op->src[0]) &&
1583                ggml_is_contiguous(op->src[1]) &&
1584                ggml_is_contiguous(op) &&
1585                ggml_are_same_shape(op->src[0], op->src[1]);
1586        case GGML_OP_GLU:
1587            switch (ggml_get_glu_op(op)) {
1588                case GGML_GLU_OP_REGLU:
1589                case GGML_GLU_OP_GEGLU:
1590                case GGML_GLU_OP_SWIGLU:
1591                case GGML_GLU_OP_SWIGLU_OAI:
1592                case GGML_GLU_OP_GEGLU_ERF:
1593                case GGML_GLU_OP_GEGLU_QUICK:
1594                case GGML_GLU_OP_SWIGLU_CLAMP:
1595                    return ggml_is_contiguous_1(op->src[0]) && (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16);
1596               default:
1597                    return false;
1598            }
1599        case GGML_OP_NONE:
1600        case GGML_OP_RESHAPE:
1601        case GGML_OP_VIEW:
1602        case GGML_OP_TRANSPOSE:
1603        case GGML_OP_PERMUTE:
1604            return true;
1605        case GGML_OP_CONCAT:
1606            {
1607                const enum ggml_type src0_type = op->src[0]->type;
1608                const enum ggml_type src1_type = op->src[1]->type;
1609                if (src0_type != src1_type || src0_type != op->type) {
1610                    return false;
1611                }
1612                switch (src0_type) {
1613                    case GGML_TYPE_F32:
1614                    case GGML_TYPE_F16:
1615                    case GGML_TYPE_I8:
1616                    case GGML_TYPE_I16:
1617                    case GGML_TYPE_I32:
1618                    case GGML_TYPE_I64:
1619                        return true;
1620                    case GGML_TYPE_BF16:
1621                        return has_bfloat;
1622                    case GGML_TYPE_Q4_0:
1623                    case GGML_TYPE_Q4_1:
1624                    case GGML_TYPE_Q5_0:
1625                    case GGML_TYPE_Q5_1:
1626                    case GGML_TYPE_Q8_0:
1627                        return true;
1628                    default:
1629                        return false;
1630                }
1631            }
1632        case GGML_OP_ADD:
1633        case GGML_OP_SUB:
1634        case GGML_OP_MUL:
1635        case GGML_OP_DIV:
1636        case GGML_OP_ADD_ID:
1637            return ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous_rows(op->src[1]) && (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16) && (op->src[0]->type == op->src[1]->type);
1638        case GGML_OP_ACC:
1639            return ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous_rows(op->src[1]) && op->src[0]->type == GGML_TYPE_F32;
1640        case GGML_OP_REPEAT:
1641        case GGML_OP_CONV_TRANSPOSE_1D:
1642            return true;
1643        case GGML_OP_CONV_TRANSPOSE_2D:
1644            return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]) &&
1645                (op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32) &&
1646                op->src[1]->type == GGML_TYPE_F32 &&
1647                op->type == GGML_TYPE_F32;
1648        case GGML_OP_COL2IM_1D:
1649            return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_BF16) &&
1650                op->type == op->src[0]->type &&
1651                ggml_is_contiguous(op->src[0]) &&
1652                ggml_is_contiguous(op);
1653        case GGML_OP_CONV_3D:
1654            return ggml_is_contiguous(op->src[0]) &&
1655                   ggml_is_contiguous(op->src[1]) &&
1656                   (op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32) &&
1657                   op->src[1]->type == GGML_TYPE_F32;
1658        case GGML_OP_SUM:
1659            return has_simdgroup_reduction && ggml_is_contiguous(op->src[0]);
1660        case GGML_OP_TRI:
1661            return ggml_is_contiguous_rows(op->src[0]);
1662        case GGML_OP_SUM_ROWS:
1663        case GGML_OP_CUMSUM:
1664        case GGML_OP_MEAN:
1665        case GGML_OP_SOFT_MAX:
1666        case GGML_OP_GROUP_NORM:
1667        case GGML_OP_L2_NORM:
1668            return has_simdgroup_reduction && ggml_is_contiguous_rows(op->src[0]);
1669        case GGML_OP_COUNT_EQUAL:
1670            return has_simdgroup_reduction &&
1671                op->src[0]->type == GGML_TYPE_I32 &&
1672                op->src[1]->type == GGML_TYPE_I32 &&
1673                op->type == GGML_TYPE_I64;
1674        case GGML_OP_ARGMAX:
1675            return has_simdgroup_reduction;
1676        case GGML_OP_NORM:
1677        case GGML_OP_RMS_NORM:
1678            return has_simdgroup_reduction && (ggml_is_contiguous_rows(op->src[0]));
1679        case GGML_OP_ROPE:
1680        case GGML_OP_ROPE_BACK:
1681            return true;
1682        case GGML_OP_IM2COL:
1683            return ggml_is_contiguous(op->src[1]) && op->src[1]->type == GGML_TYPE_F32 && (op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_F32);
1684        case GGML_OP_CONV_2D:
1685            return ggml_is_contiguous(op->src[0]) &&
1686                   op->src[1]->type == GGML_TYPE_F32 &&
1687                   op->type == GGML_TYPE_F32 &&
1688                   (op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32);
1689        case GGML_OP_CONV_2D_DW:
1690            return op->src[1]->type == GGML_TYPE_F32 &&
1691                   op->type == GGML_TYPE_F32 &&
1692                   (op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32);
1693        case GGML_OP_UPSCALE:
1694            return op->src[0]->type == GGML_TYPE_F32;
1695        case GGML_OP_POOL_1D:
1696            return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32;
1697        case GGML_OP_POOL_2D:
1698            return op->src[0]->type == GGML_TYPE_F32;
1699        case GGML_OP_PAD:
1700            // TODO: add circular padding support for metal, see https://github.com/ggml-org/llama.cpp/pull/16985
1701            if (ggml_get_op_params_i32(op, 8) != 0) {
1702                return false;
1703            }
1704
1705            return (ggml_get_op_params_i32(op, 0) == 0) && (ggml_get_op_params_i32(op, 2) == 0) &&
1706                   (ggml_get_op_params_i32(op, 4) == 0) && (ggml_get_op_params_i32(op, 6) == 0);
1707        case GGML_OP_PAD_REFLECT_1D:
1708        case GGML_OP_TIMESTEP_EMBEDDING:
1709            return op->src[0]->type == GGML_TYPE_F32;
1710        case GGML_OP_LEAKY_RELU:
1711            return op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16;
1712        case GGML_OP_ARGSORT:
1713        case GGML_OP_TOP_K:
1714        case GGML_OP_ARANGE:
1715            return true;
1716        case GGML_OP_ROLL:
1717            return ggml_is_contiguous(op->src[0]);
1718        case GGML_OP_FLASH_ATTN_EXT:
1719            // for new head sizes, add checks here
1720            if (op->src[0]->ne[0] != 32 &&
1721                op->src[0]->ne[0] != 40 &&
1722                op->src[0]->ne[0] != 48 &&
1723                op->src[0]->ne[0] != 64 &&
1724                op->src[0]->ne[0] != 72 &&
1725                op->src[0]->ne[0] != 80 &&
1726                op->src[0]->ne[0] != 96 &&
1727                op->src[0]->ne[0] != 112 &&
1728                op->src[0]->ne[0] != 128 &&
1729                op->src[0]->ne[0] != 192 &&
1730                op->src[0]->ne[0] != 256 &&
1731                op->src[0]->ne[0] != 320 &&
1732                op->src[0]->ne[0] != 512 &&
1733                op->src[0]->ne[0] != 576) {
1734                return false;
1735            }
1736            if (op->src[1]->type != op->src[2]->type) {
1737                return false;
1738            }
1739            switch (op->src[1]->type) {
1740                case GGML_TYPE_F32:
1741                case GGML_TYPE_F16:
1742                case GGML_TYPE_Q8_0:
1743                case GGML_TYPE_Q4_0:
1744                case GGML_TYPE_Q4_1:
1745                case GGML_TYPE_Q5_0:
1746                case GGML_TYPE_Q5_1:
1747                    break;
1748                case GGML_TYPE_BF16:
1749                    if (!has_bfloat) {
1750                        return false;
1751                    }
1752                    break;
1753                default:
1754                    return false;
1755            }
1756            return has_simdgroup_mm; // TODO: over-restricted for vec-kernels
1757        case GGML_OP_LIGHTNING_INDEXER:
1758            if (op->src[0]->ne[0] != OP_LIGHTNING_INDEXER_DK ||
1759                op->src[0]->ne[1] != OP_LIGHTNING_INDEXER_NH) {
1760                return false;
1761            }
1762            if (!has_simdgroup_mm ||
1763                op->src[0]->type != GGML_TYPE_F32 ||
1764                op->src[2]->type != GGML_TYPE_F32 ||
1765                op->src[3]->type != GGML_TYPE_F16 ||
1766                op->type         != GGML_TYPE_F32 ||
1767                !ggml_is_contiguous_rows(op->src[0]) ||
1768                !ggml_is_contiguous_rows(op->src[1]) ||
1769                !ggml_is_contiguous_rows(op->src[2]) ||
1770                !ggml_is_contiguous_rows(op->src[3])) {
1771                return false;
1772            }
1773            switch (op->src[1]->type) {
1774                case GGML_TYPE_F32:
1775                case GGML_TYPE_F16:
1776                case GGML_TYPE_Q4_0:
1777                case GGML_TYPE_Q4_1:
1778                case GGML_TYPE_Q5_0:
1779                case GGML_TYPE_Q5_1:
1780                case GGML_TYPE_Q8_0:
1781                    return true;
1782                case GGML_TYPE_BF16:
1783                    return has_bfloat;
1784                default:
1785                    return false;
1786            }
1787        case GGML_OP_DSV4_HC_COMB:
1788            return has_simdgroup_reduction &&
1789                op->src[0]->type == GGML_TYPE_F32 &&
1790                op->src[1]->type == GGML_TYPE_F32 &&
1791                op->src[2]->type == GGML_TYPE_F32 &&
1792                op->type         == GGML_TYPE_F32 &&
1793                op->src[0]->ne[0] == 24 &&
1794                op->src[1]->ne[0] >= 3 &&
1795                op->src[2]->ne[0] == 24 &&
1796                ggml_is_contiguous_rows(op->src[0]) &&
1797                ggml_is_contiguous_rows(op->src[1]) &&
1798                ggml_is_contiguous_rows(op->src[2]);
1799        case GGML_OP_DSV4_HC_PRE:
1800            return has_simdgroup_reduction &&
1801                op->src[0]->type == GGML_TYPE_F32 &&
1802                op->src[1]->type == GGML_TYPE_F32 &&
1803                op->type         == GGML_TYPE_F32 &&
1804                op->src[0]->ne[1] == 4 &&
1805                op->src[1]->ne[0] == 4 &&
1806                ggml_is_contiguous_rows(op->src[0]) &&
1807                ggml_is_contiguous_rows(op->src[1]);
1808        case GGML_OP_DSV4_HC_POST:
1809            return has_simdgroup_reduction &&
1810                op->src[0]->type == GGML_TYPE_F32 &&
1811                op->src[1]->type == GGML_TYPE_F32 &&
1812                op->src[2]->type == GGML_TYPE_F32 &&
1813                op->src[3]->type == GGML_TYPE_F32 &&
1814                op->type         == GGML_TYPE_F32 &&
1815                op->src[1]->ne[1] == 4 &&
1816                op->src[2]->ne[0] == 4 &&
1817                op->src[3]->ne[0] == 4 &&
1818                op->src[3]->ne[1] == 4 &&
1819                ggml_is_contiguous_rows(op->src[0]) &&
1820                ggml_is_contiguous_rows(op->src[1]) &&
1821                ggml_is_contiguous_rows(op->src[2]) &&
1822                ggml_is_contiguous_rows(op->src[3]);
1823        case GGML_OP_SSM_SCAN:
1824            return has_simdgroup_reduction;
1825        case GGML_OP_SSM_CONV:
1826            return has_simdgroup_reduction;
1827        case GGML_OP_RWKV_WKV6:
1828        case GGML_OP_RWKV_WKV7:
1829            return true;
1830        case GGML_OP_GATED_DELTA_NET:
1831            return has_simdgroup_reduction && op->src[2]->ne[0] % 32 == 0;
1832        case GGML_OP_SOLVE_TRI:
1833            return has_simdgroup_reduction && op->src[0]->type == GGML_TYPE_F32;
1834        case GGML_OP_MUL_MAT:
1835            return ggml_metal_supports_mul_mat_op(
1836                    has_simdgroup_reduction, op, true,
1837                    ggml_metal_op_mul_mat_use_mm(op, has_simdgroup_mm));
1838        case GGML_OP_MUL_MAT_ID:
1839            return ggml_metal_supports_mul_mat_op(
1840                    has_simdgroup_reduction, op, false,
1841                    ggml_metal_op_mul_mat_id_use_mm(op, has_simdgroup_mm));
1842        case GGML_OP_SET:
1843        case GGML_OP_CPY:
1844        case GGML_OP_DUP:
1845        case GGML_OP_CONT:
1846            {
1847                switch (op->src[0]->type) {
1848                    case GGML_TYPE_F32:
1849                        switch (op->type) {
1850                           case GGML_TYPE_F32:
1851                           case GGML_TYPE_F16:
1852                           case GGML_TYPE_BF16:
1853                           case GGML_TYPE_Q8_0:
1854                           case GGML_TYPE_Q1_0:
1855                           case GGML_TYPE_Q2_0:
1856                           case GGML_TYPE_Q4_0:
1857                           case GGML_TYPE_Q4_1:
1858                           case GGML_TYPE_Q5_0:
1859                           case GGML_TYPE_Q5_1:
1860                           case GGML_TYPE_IQ4_NL:
1861                           case GGML_TYPE_TQ2_0:
1862                           case GGML_TYPE_I32:
1863                                return true;
1864                           default:
1865                                return false;
1866                        }
1867                    case GGML_TYPE_F16:
1868                        switch (op->type) {
1869                            case GGML_TYPE_F32:
1870                            case GGML_TYPE_F16:
1871                                return true;
1872                            default:
1873                                return false;
1874                        }
1875                    case GGML_TYPE_BF16:
1876                        switch (op->type) {
1877                            case GGML_TYPE_F32:
1878                            case GGML_TYPE_BF16:
1879                                return true;
1880                            default:
1881                                return false;
1882                        }
1883                    case GGML_TYPE_Q1_0:
1884                    case GGML_TYPE_Q2_0:
1885                    case GGML_TYPE_Q4_0:
1886                    case GGML_TYPE_Q4_1:
1887                    case GGML_TYPE_Q5_0:
1888                    case GGML_TYPE_Q5_1:
1889                    case GGML_TYPE_Q8_0:
1890                    case GGML_TYPE_TQ2_0:
1891                        switch (op->type) {
1892                            case GGML_TYPE_F32:
1893                            case GGML_TYPE_F16:
1894                                return true;
1895                            default:
1896                                return false;
1897                        }
1898                    case GGML_TYPE_I32:
1899                        return op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_I32;
1900                    default:
1901                        return false;
1902                };
1903            }
1904        case GGML_OP_GET_ROWS:
1905            return op->src[0]->type != GGML_TYPE_NVFP4 &&
1906                   op->src[0]->type != GGML_TYPE_TQ1_0;
1907        case GGML_OP_SET_ROWS:
1908            {
1909                if (op->src[0]->type == GGML_TYPE_F16) {
1910                    return op->type == GGML_TYPE_F16;
1911                }
1912
1913                if (op->src[0]->type != GGML_TYPE_F32) {
1914                    return false;
1915                }
1916
1917                switch (op->type) {
1918                    case GGML_TYPE_F32:
1919                    case GGML_TYPE_F16:
1920                    case GGML_TYPE_BF16:
1921                    case GGML_TYPE_Q8_0:
1922                    case GGML_TYPE_Q4_0:
1923                    case GGML_TYPE_Q4_1:
1924                    case GGML_TYPE_Q5_0:
1925                    case GGML_TYPE_Q5_1:
1926                    case GGML_TYPE_IQ4_NL:
1927                    case GGML_TYPE_TQ2_0:
1928                        return true;
1929                    default:
1930                        return false;
1931                };
1932            }
1933        case GGML_OP_DIAG:
1934            return true;
1935        case GGML_OP_OPT_STEP_ADAMW:
1936        case GGML_OP_OPT_STEP_SGD:
1937            return has_simdgroup_reduction;
1938        default:
1939            return false;
1940    }
1941}
1942
1943const struct ggml_metal_device_props * ggml_metal_device_get_props(ggml_metal_device_t dev) {
1944    return &dev->props;
1945}
1946
1947static void ggml_metal_device_disable_tensor(ggml_metal_device_t dev) {
1948    dev->props.has_tensor = false;
1949}
1950
1951struct ggml_metal_fusion_info * ggml_metal_device_get_fusion_info(ggml_metal_device_t dev) {
1952    return dev->finfo;
1953}
1954
1955//
1956// device buffers
1957//
1958
1959// max memory buffers that can be mapped to the device
1960#define GGML_METAL_MAX_BUFFERS 64
1961
1962struct ggml_metal_buffer_wrapper {
1963    void   * data;
1964    size_t   size;
1965
1966    id<MTLBuffer> metal;
1967};
1968
1969struct ggml_metal_buffer {
1970    void * all_data;
1971    size_t all_size;
1972
1973    // if false, the Metal buffer data is allocated in private GPU memory and is not shared with the host
1974    bool is_shared;
1975    bool owned;
1976
1977    // multiple buffers are used only to avoid the maximum buffer size limitation when using mmap
1978    int n_buffers;
1979    struct ggml_metal_buffer_wrapper buffers[GGML_METAL_MAX_BUFFERS];
1980
1981    bool use_residency_sets;
1982
1983    // optional MTLResidencySet
1984    // note: cannot use explicitly "id<MTLResidencySet>" here because it is not available on certain OSes
1985    id rset;
1986
1987    // pointers to global device
1988    ggml_metal_device_t dev;
1989};
1990
1991static void ggml_metal_log_allocated_size(id<MTLDevice> device, size_t size_aligned) {
1992#ifndef GGML_METAL_NDEBUG
1993#if TARGET_OS_OSX || (TARGET_OS_IOS && __clang_major__ >= 15)
1994    if (@available(macOS 10.12, iOS 16.0, *)) {
1995        GGML_LOG_DEBUG("%s: allocated buffer, size = %8.2f MiB, (%8.2f / %8.2f)\n",
1996                __func__,
1997                size_aligned / 1024.0 / 1024.0,
1998                device.currentAllocatedSize / 1024.0 / 1024.0,
1999                device.recommendedMaxWorkingSetSize / 1024.0 / 1024.0);
2000
2001        if (device.currentAllocatedSize > device.recommendedMaxWorkingSetSize) {
2002            GGML_LOG_WARN("%s: warning: current allocated size is greater than the recommended max working set size\n", __func__);
2003        }
2004    } else {
2005        GGML_LOG_INFO("%s: allocated buffer, size = %8.2f MiB, (%8.2f)\n",
2006                __func__,
2007                size_aligned / 1024.0 / 1024.0,
2008                device.currentAllocatedSize / 1024.0 / 1024.0);
2009    }
2010#endif
2011#endif
2012    GGML_UNUSED(device);
2013    GGML_UNUSED(size_aligned);
2014}
2015
2016// rset init
2017static bool ggml_metal_buffer_rset_init(ggml_metal_buffer_t buf) {
2018    buf->rset = nil;
2019
2020    if (!buf->use_residency_sets) {
2021        return true;
2022    }
2023
2024#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
2025    if (@available(macOS 15.0, iOS 18.0, tvOS 18.0, visionOS 2.0, *)) {
2026        MTLResidencySetDescriptor * desc = [[MTLResidencySetDescriptor alloc] init];
2027        desc.label = @"ggml_metal";
2028        desc.initialCapacity = buf->n_buffers;
2029
2030        NSError * error;
2031        buf->rset = [buf->dev->mtl_device newResidencySetWithDescriptor:desc error:&error];
2032        if (error) {
2033            GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);
2034            [desc release];
2035            return false;
2036        }
2037
2038        [desc release];
2039
2040        for (int i = 0; i < buf->n_buffers; i++) {
2041            [buf->rset addAllocation:buf->buffers[i].metal];
2042        }
2043
2044        [buf->rset commit];
2045        [buf->rset requestResidency];
2046
2047        return true;
2048    }
2049#endif
2050
2051    return true;
2052}
2053
2054// rset free
2055static void ggml_metal_buffer_rset_free(ggml_metal_buffer_t buf) {
2056#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
2057    if (@available(macOS 15.0, iOS 18.0, tvOS 18.0, visionOS 2.0, *)) {
2058        if (buf->rset) {
2059            [buf->rset endResidency];
2060            [buf->rset removeAllAllocations];
2061            [buf->rset commit];
2062            [buf->rset release];
2063        }
2064    }
2065#else
2066    GGML_UNUSED(buf);
2067#endif
2068}
2069
2070static void * ggml_metal_host_malloc(size_t n) {
2071    void * data = NULL;
2072
2073#if TARGET_OS_OSX
2074    kern_return_t err = vm_allocate((vm_map_t) mach_task_self(), (void *) &data, n, VM_FLAGS_ANYWHERE);
2075    if (err != KERN_SUCCESS) {
2076        GGML_LOG_ERROR("%s: error: vm_allocate failed\n", __func__);
2077        return NULL;
2078    }
2079#else
2080    const int result = posix_memalign((void **) &data, sysconf(_SC_PAGESIZE), n);
2081    if (result != 0) {
2082        GGML_LOG_ERROR("%s: error: posix_memalign failed\n", __func__);
2083        return NULL;
2084    }
2085#endif
2086
2087    return data;
2088}
2089
2090ggml_metal_buffer_t ggml_metal_buffer_init(ggml_metal_device_t dev, size_t size, bool shared) {
2091    ggml_metal_buffer_t res = calloc(1, sizeof(struct ggml_metal_buffer));
2092
2093    res->dev = dev;
2094
2095    const size_t size_page = sysconf(_SC_PAGESIZE);
2096
2097    size_t size_aligned = size;
2098    if ((size_aligned % size_page) != 0) {
2099        size_aligned += (size_page - (size_aligned % size_page));
2100    }
2101
2102    const struct ggml_metal_device_props * props_dev = ggml_metal_device_get_props(dev);
2103
2104    shared = shared && props_dev->use_shared_buffers;
2105
2106    // allocate shared buffer if the device supports it and it is required by the buffer type
2107    if (shared) {
2108        res->all_data = ggml_metal_host_malloc(size_aligned);
2109        res->is_shared = true;
2110    } else {
2111        // use virtual address
2112        res->all_data = (void *) atomic_fetch_add_explicit(&dev->addr_virt, size_aligned, memory_order_relaxed);
2113        res->is_shared = false;
2114    }
2115    res->all_size = size_aligned;
2116
2117    res->owned = true;
2118
2119    res->n_buffers = 1;
2120
2121    if (res->all_data != NULL) {
2122        res->buffers[0].size  = size;
2123        res->buffers[0].metal = nil;
2124
2125        if (size_aligned > 0) {
2126            if (props_dev->use_shared_buffers && shared) {
2127                res->buffers[0].metal = [res->dev->mtl_device newBufferWithBytesNoCopy:res->all_data
2128                                                                  length:size_aligned
2129                                                                 options:MTLResourceStorageModeShared
2130                                                             deallocator:nil];
2131            } else {
2132                res->buffers[0].metal = [res->dev->mtl_device newBufferWithLength:size_aligned options:MTLResourceStorageModePrivate];
2133            }
2134        }
2135
2136        res->buffers[0].data = res->all_data;
2137    }
2138
2139    if (size_aligned > 0 && (res->all_data == NULL || res->buffers[0].metal == nil)) {
2140        GGML_LOG_ERROR("%s: error: failed to allocate buffer, size = %8.2f MiB\n", __func__, size_aligned / 1024.0 / 1024.0);
2141        free(res);
2142        return NULL;
2143    }
2144
2145    res->use_residency_sets = props_dev->use_residency_sets;
2146
2147    if (!ggml_metal_buffer_rset_init(res)) {
2148        GGML_LOG_ERROR("%s: error: failed to initialize residency set\n", __func__);
2149        free(res);
2150        return NULL;
2151    }
2152
2153    ggml_metal_device_rsets_add(dev, res->rset);
2154
2155    //ggml_metal_log_allocated_size(device, size_aligned);
2156
2157    return res;
2158}
2159
2160ggml_metal_buffer_t ggml_metal_buffer_map(ggml_metal_device_t dev, void * ptr, size_t size, size_t max_tensor_size) {
2161    ggml_metal_buffer_t res = calloc(1, sizeof(struct ggml_metal_buffer));
2162
2163    res->dev = dev;
2164
2165    res->all_data = ptr;
2166    res->all_size = size;
2167
2168    res->is_shared = true;
2169    res->owned = false;
2170
2171    res->n_buffers = 0;
2172
2173    const size_t size_page = sysconf(_SC_PAGESIZE);
2174
2175    // page-align the data ptr
2176    {
2177        const uintptr_t offs = (uintptr_t) ptr % size_page;
2178        ptr  = (void *) ((char *) ptr - offs);
2179        size += offs;
2180    }
2181
2182    size_t size_aligned = size;
2183    if ((size_aligned % size_page) != 0) {
2184        size_aligned += (size_page - (size_aligned % size_page));
2185    }
2186
2187    const struct ggml_metal_device_props * props_dev = ggml_metal_device_get_props(dev);
2188
2189    // the buffer fits into the max buffer size allowed by the device
2190    if (size_aligned <= props_dev->max_buffer_size) {
2191        res->buffers[res->n_buffers].data  = ptr;
2192        res->buffers[res->n_buffers].size  = size;
2193        res->buffers[res->n_buffers].metal = nil;
2194
2195        if (size_aligned > 0) {
2196            res->buffers[res->n_buffers].metal = [res->dev->mtl_device newBufferWithBytesNoCopy:ptr length:size_aligned options:MTLResourceStorageModeShared deallocator:nil];
2197
2198            if (res->buffers[res->n_buffers].metal == nil) {
2199                GGML_LOG_ERROR("%s: error: failed to allocate buffer, size = %8.2f MiB\n", __func__, size_aligned / 1024.0 / 1024.0);
2200                free(res);
2201                return NULL;
2202            }
2203        }
2204
2205        ggml_metal_log_allocated_size(res->dev->mtl_device, size_aligned);
2206
2207        ++res->n_buffers;
2208    } else {
2209        // this overlap between the views will guarantee that the tensor with the maximum size will fully fit into
2210        // one of the views
2211        const size_t size_ovlp = ((max_tensor_size + size_page - 1) / size_page + 1) * size_page; // round-up 2 pages just in case
2212        const size_t size_step = props_dev->max_buffer_size - size_ovlp;
2213        const size_t size_view = props_dev->max_buffer_size;
2214
2215        for (size_t i = 0; i < size; i += size_step) {
2216            const size_t size_step_aligned = (i + size_view <= size) ? size_view : (size_aligned - i);
2217
2218            res->buffers[res->n_buffers].data  = (void *) ((uint8_t *) ptr + i);
2219            res->buffers[res->n_buffers].size  = size_step_aligned;
2220            res->buffers[res->n_buffers].metal = nil;
2221
2222            if (size_step_aligned > 0) {
2223                res->buffers[res->n_buffers].metal = [res->dev->mtl_device newBufferWithBytesNoCopy:(void *) ((uint8_t *) ptr + i) length:size_step_aligned options:MTLResourceStorageModeShared deallocator:nil];
2224
2225                if (res->buffers[res->n_buffers].metal == nil) {
2226                    GGML_LOG_ERROR("%s: error: failed to allocate buffer, size = %8.2f MiB\n", __func__, size_step_aligned / 1024.0 / 1024.0);
2227                    free(res);
2228                    return NULL;
2229                }
2230            }
2231
2232            ggml_metal_log_allocated_size(res->dev->mtl_device, size_step_aligned);
2233
2234            if (i + size_step < size) {
2235                GGML_LOG_INFO("\n");
2236            }
2237
2238            ++res->n_buffers;
2239        }
2240    }
2241
2242    res->use_residency_sets = props_dev->use_residency_sets;
2243
2244    if (!ggml_metal_buffer_rset_init(res)) {
2245        GGML_LOG_ERROR("%s: error: failed to initialize residency set\n", __func__);
2246        free(res);
2247        return NULL;
2248    }
2249
2250    ggml_metal_device_rsets_add(dev, res->rset);
2251
2252    return res;
2253}
2254
2255void ggml_metal_buffer_free(ggml_metal_buffer_t buf) {
2256    @autoreleasepool {
2257        ggml_metal_device_rsets_rm(buf->dev, buf->rset);
2258
2259        for (int i = 0; i < buf->n_buffers; i++) {
2260            [buf->buffers[i].metal release];
2261        }
2262
2263        ggml_metal_buffer_rset_free(buf);
2264    }
2265
2266    if (buf->is_shared && buf->owned) {
2267#if TARGET_OS_OSX
2268        vm_deallocate((vm_map_t)mach_task_self(), (vm_address_t)buf->all_data, buf->all_size);
2269#else
2270        free(buf->all_data);
2271#endif
2272    }
2273
2274    free(buf);
2275}
2276
2277void * ggml_metal_buffer_get_base(ggml_metal_buffer_t buf) {
2278    return buf->all_data;
2279}
2280
2281bool ggml_metal_buffer_is_shared(ggml_metal_buffer_t buf) {
2282    return buf->is_shared;
2283}
2284
2285void ggml_metal_buffer_memset_tensor(ggml_metal_buffer_t buf, struct ggml_tensor * tensor, uint8_t value, size_t offset, size_t size) {
2286    if (buf->is_shared) {
2287        memset((char *) tensor->data + offset, value, size);
2288        return;
2289    }
2290
2291    @autoreleasepool {
2292        // dst
2293        struct ggml_metal_buffer_id bid_dst = ggml_metal_buffer_get_id(buf, tensor);
2294        bid_dst.offs += offset;
2295
2296        id<MTLCommandBuffer> cmd_buf = [buf->dev->mtl_queue commandBufferWithUnretainedReferences];
2297
2298        {
2299            id<MTLBlitCommandEncoder> encoder = [cmd_buf blitCommandEncoder];
2300
2301            [encoder fillBuffer:bid_dst.metal
2302                          range:NSMakeRange(bid_dst.offs, bid_dst.offs + size)
2303                          value:value];
2304
2305            [encoder endEncoding];
2306        }
2307
2308        [cmd_buf commit];
2309        [cmd_buf waitUntilCompleted];
2310    }
2311}
2312
2313void ggml_metal_buffer_set_tensor(ggml_metal_buffer_t buf, struct ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
2314    if (buf->is_shared) {
2315        memcpy((char *) tensor->data + offset, data, size);
2316        return;
2317    }
2318
2319    @autoreleasepool {
2320        // src
2321        void * data_ptr = (void *)(uintptr_t) data; // "const cast" the src data
2322        id<MTLBuffer> buf_src = [buf->dev->mtl_device newBufferWithBytesNoCopy:data_ptr
2323                                                               length:size
2324                                                              options:MTLResourceStorageModeShared
2325                                                          deallocator:nil];
2326
2327        GGML_ASSERT(buf_src);
2328
2329        // dst
2330        struct ggml_metal_buffer_id bid_dst = ggml_metal_buffer_get_id(buf, tensor);
2331        bid_dst.offs += offset;
2332
2333        // note: for experimentation purposes, here we use a semaphore to wait for the copy to complete
2334        //       this is alternative to waitUntilCompleted, which should be faster, but don't seem to make much difference
2335        dispatch_semaphore_t completion_semaphore = dispatch_semaphore_create(0);
2336
2337        id<MTLCommandBuffer> cmd_buf = [buf->dev->mtl_queue commandBufferWithUnretainedReferences];
2338
2339        {
2340            id<MTLBlitCommandEncoder> encoder = [cmd_buf blitCommandEncoder];
2341
2342            [encoder copyFromBuffer:buf_src
2343                       sourceOffset:0
2344                           toBuffer:bid_dst.metal
2345                  destinationOffset:bid_dst.offs
2346                               size:size];
2347
2348            [encoder endEncoding];
2349        }
2350
2351        [cmd_buf addCompletedHandler:^(id<MTLCommandBuffer> cb) {
2352                             // TODO: can check for errors here
2353            GGML_UNUSED(cb);
2354
2355            dispatch_semaphore_signal(completion_semaphore);
2356        }];
2357
2358        [cmd_buf commit];
2359
2360        dispatch_semaphore_wait(completion_semaphore, DISPATCH_TIME_FOREVER);
2361        dispatch_release(completion_semaphore);
2362
2363        //[cmd_buf waitUntilCompleted];
2364    }
2365}
2366
2367void ggml_metal_buffer_get_tensor(ggml_metal_buffer_t buf, const struct ggml_tensor * tensor, void * data, size_t offset, size_t size) {
2368    if (buf->is_shared) {
2369        memcpy(data, (const char *) tensor->data + offset, size);
2370        return;
2371    }
2372
2373    @autoreleasepool {
2374        // src
2375        struct ggml_metal_buffer_id bid_src = ggml_metal_buffer_get_id(buf, tensor);
2376        bid_src.offs += offset;
2377
2378        // dst
2379        id<MTLBuffer> buf_dst = [buf->dev->mtl_device newBufferWithBytesNoCopy:data
2380                                                               length:size
2381                                                              options:MTLResourceStorageModeShared
2382                                                          deallocator:nil];
2383
2384        GGML_ASSERT(buf_dst);
2385
2386        id<MTLCommandBuffer> cmd_buf = [buf->dev->mtl_queue commandBufferWithUnretainedReferences];
2387
2388        {
2389            id<MTLBlitCommandEncoder> encoder = [cmd_buf blitCommandEncoder];
2390
2391            [encoder copyFromBuffer:bid_src.metal
2392                       sourceOffset:bid_src.offs
2393                           toBuffer:buf_dst
2394                  destinationOffset:0
2395                               size:size];
2396
2397            [encoder endEncoding];
2398        }
2399
2400        [cmd_buf commit];
2401        [cmd_buf waitUntilCompleted];
2402    }
2403}
2404
2405bool ggml_metal_buffer_cpy_tensor(ggml_metal_buffer_t buf_dst, const struct ggml_tensor * src, struct ggml_tensor * dst) {
2406    ggml_metal_buffer_t buf_src = (ggml_metal_buffer_t)src->buffer->context;
2407
2408    const size_t size = ggml_nbytes(src);
2409
2410    // if both buffers are shared, we can use memcpy directly
2411    if (buf_dst->is_shared && buf_src->is_shared) {
2412        memcpy(dst->data, src->data, size);
2413        return true;
2414    }
2415
2416    // for private buffers, we need to use Metal blit commands
2417    @autoreleasepool {
2418        struct ggml_metal_buffer_id bid_src = ggml_metal_buffer_get_id(buf_src, src);
2419        struct ggml_metal_buffer_id bid_dst = ggml_metal_buffer_get_id(buf_dst, dst);
2420
2421        if (bid_src.metal == nil || bid_dst.metal == nil) {
2422            return false;
2423        }
2424
2425        id<MTLCommandBuffer> cmd_buf = [buf_dst->dev->mtl_queue commandBufferWithUnretainedReferences];
2426
2427        {
2428            id<MTLBlitCommandEncoder> encoder = [cmd_buf blitCommandEncoder];
2429
2430            [encoder copyFromBuffer:bid_src.metal
2431                       sourceOffset:bid_src.offs
2432                           toBuffer:bid_dst.metal
2433                  destinationOffset:bid_dst.offs
2434                               size:size];
2435
2436            [encoder endEncoding];
2437        }
2438
2439        [cmd_buf commit];
2440        [cmd_buf waitUntilCompleted];
2441    }
2442
2443    return true;
2444}
2445
2446void ggml_metal_buffer_clear(ggml_metal_buffer_t buf, uint8_t value) {
2447    if (buf->is_shared) {
2448        memset(buf->all_data, value, buf->all_size);
2449        return;
2450    }
2451
2452    @autoreleasepool {
2453        id<MTLCommandBuffer> cmd_buf = [buf->dev->mtl_queue commandBufferWithUnretainedReferences];
2454
2455        {
2456            id<MTLBlitCommandEncoder> encoder = [cmd_buf blitCommandEncoder];
2457
2458            [encoder fillBuffer:buf->buffers[0].metal
2459                          range:NSMakeRange(0, buf->buffers[0].size)
2460                          value:value];
2461
2462            [encoder endEncoding];
2463        }
2464
2465        [cmd_buf commit];
2466        [cmd_buf waitUntilCompleted];
2467    }
2468}
2469
2470struct ggml_metal_buffer_id ggml_metal_buffer_get_id(ggml_metal_buffer_t buf, const struct ggml_tensor * t) {
2471    struct ggml_metal_buffer_id res = { nil, 0 };
2472
2473    const int64_t tsize = ggml_nbytes(t);
2474
2475    // find the view that contains the tensor fully
2476    for (int i = 0; i < buf->n_buffers; ++i) {
2477        const int64_t ioffs = (int64_t) t->data - (int64_t) buf->buffers[i].data;
2478
2479        //GGML_LOG_INFO("ioffs = %10ld, tsize = %10ld, sum = %10ld, buf->buffers[%d].size = %10ld\n", ioffs, tsize, ioffs + tsize, i, buf->buffers[i].size);
2480        if (ioffs >= 0 && ioffs + tsize <= (int64_t) buf->buffers[i].size) {
2481            res.metal = buf->buffers[i].metal;
2482            res.offs  = (size_t) ioffs;
2483
2484            //GGML_LOG_INFO("%s: tensor '%16s', offs = %8ld\n", __func__, t->name, *offs);
2485
2486            return res;
2487        }
2488    }
2489
2490    GGML_LOG_ERROR("%s: error: tensor '%s' buffer is nil\n", __func__, t->name);
2491
2492    return res;
2493}