| 
46 | 46 | static struct ggml_backend_metal_device_context {  | 
47 | 47 |     id<MTLDevice> mtl_device;  | 
48 | 48 |     int           mtl_device_ref_count;  | 
 | 49 | +    id<MTLLibrary> mtl_library;  | 
49 | 50 | 
 
  | 
50 | 51 |     bool has_simdgroup_reduction;  | 
51 | 52 |     bool has_simdgroup_mm;  | 
 | 
57 | 58 | } g_ggml_ctx_dev_main = {  | 
58 | 59 |     /*.mtl_device              =*/ nil,  | 
59 | 60 |     /*.mtl_device_ref_count    =*/ 0,  | 
 | 61 | +    /*.mtl_library             =*/ nil,  | 
60 | 62 |     /*.has_simdgroup_reduction =*/ false,  | 
61 | 63 |     /*.has_simdgroup_mm        =*/ false,  | 
62 | 64 |     /*.has_residency_sets      =*/ false,  | 
@@ -108,6 +110,11 @@ static void ggml_backend_metal_device_rel(struct ggml_backend_metal_device_conte  | 
108 | 110 |     ctx->mtl_device_ref_count--;  | 
109 | 111 | 
 
  | 
110 | 112 |     if (ctx->mtl_device_ref_count == 0) {  | 
 | 113 | +        if (ctx->mtl_library) {  | 
 | 114 | +            [ctx->mtl_library release];  | 
 | 115 | +            ctx->mtl_library = nil;  | 
 | 116 | +        }  | 
 | 117 | + | 
111 | 118 |         if (ctx->mtl_device) {  | 
112 | 119 |             [ctx->mtl_device release];  | 
113 | 120 |             ctx->mtl_device = nil;  | 
@@ -495,163 +502,174 @@ @implementation GGMLMetalClass  | 
495 | 502 |     return data;  | 
496 | 503 | }  | 
497 | 504 | 
 
  | 
498 |  | -static struct ggml_backend_metal_context * ggml_metal_init(ggml_backend_dev_t dev) {  | 
499 |  | -    GGML_LOG_INFO("%s: allocating\n", __func__);  | 
500 |  | - | 
501 |  | -#if TARGET_OS_OSX && !GGML_METAL_NDEBUG  | 
502 |  | -    // Show all the Metal device instances in the system  | 
503 |  | -    NSArray * devices = MTLCopyAllDevices();  | 
504 |  | -    for (id<MTLDevice> device in devices) {  | 
505 |  | -        GGML_LOG_INFO("%s: found device: %s\n", __func__, [[device name] UTF8String]);  | 
506 |  | -    }  | 
507 |  | -    [devices release]; // since it was created by a *Copy* C method  | 
508 |  | -#endif  | 
509 |  | - | 
510 |  | -    // init context  | 
511 |  | -    struct ggml_backend_metal_context * ctx = calloc(1, sizeof(struct ggml_backend_metal_context));  | 
512 |  | -    struct ggml_backend_metal_device_context * ctx_dev = dev->context;  | 
513 |  | - | 
514 |  | -    id<MTLDevice> device = ggml_backend_metal_device_acq(ctx_dev);  | 
515 |  | -    GGML_LOG_INFO("%s: picking default device: %s\n", __func__, [[device name] UTF8String]);  | 
516 |  | - | 
517 |  | -    ctx->queue  = [device newCommandQueue];  | 
518 |  | -    if (ctx->queue == nil) {  | 
519 |  | -        GGML_LOG_ERROR("%s: error: failed to create command queue\n", __func__);  | 
520 |  | -        return NULL;  | 
521 |  | -    }  | 
522 |  | - | 
523 |  | -    ctx->d_queue = dispatch_queue_create("ggml-metal", DISPATCH_QUEUE_CONCURRENT);  | 
524 |  | - | 
 | 505 | +// load library  | 
 | 506 | +//  | 
 | 507 | +// - first check if the library is embedded  | 
 | 508 | +// - then check if the library is in the bundle  | 
 | 509 | +// - if not found, load the source and compile it  | 
 | 510 | +// - if that fails, return NULL  | 
 | 511 | +static id<MTLLibrary> ggml_metal_load_library(id<MTLDevice> device, bool use_bfloat) {  | 
525 | 512 |     id<MTLLibrary> metal_library = nil;  | 
526 |  | - | 
527 |  | -    // load library  | 
528 |  | -    //  | 
529 |  | -    // - first check if the library is embedded  | 
530 |  | -    // - then check if the library is in the bundle  | 
531 |  | -    // - if not found, load the source and compile it  | 
532 |  | -    // - if that fails, return NULL  | 
533 |  | -    {  | 
534 |  | -        NSError * error = nil;  | 
535 |  | -        NSString * src = nil;  | 
 | 513 | +    NSError * error = nil;  | 
 | 514 | +    NSString * src = nil;  | 
536 | 515 | 
 
  | 
537 | 516 | #if GGML_METAL_EMBED_LIBRARY  | 
538 |  | -        GGML_LOG_INFO("%s: using embedded metal library\n", __func__);  | 
 | 517 | +    GGML_LOG_INFO("%s: using embedded metal library\n", __func__);  | 
539 | 518 | 
 
  | 
540 |  | -        extern const char ggml_metallib_start[];  | 
541 |  | -        extern const char ggml_metallib_end[];  | 
 | 519 | +    extern const char ggml_metallib_start[];  | 
 | 520 | +    extern const char ggml_metallib_end[];  | 
542 | 521 | 
 
  | 
543 |  | -        src = [[NSString alloc] initWithBytes:ggml_metallib_start length:(ggml_metallib_end-ggml_metallib_start) encoding:NSUTF8StringEncoding];  | 
 | 522 | +    src = [[NSString alloc] initWithBytes:ggml_metallib_start length:(ggml_metallib_end-ggml_metallib_start) encoding:NSUTF8StringEncoding];  | 
544 | 523 | 
 
  | 
545 | 524 | #else  | 
546 | 525 | 
 
  | 
547 | 526 | #ifdef SWIFT_PACKAGE  | 
548 |  | -        NSBundle * bundle = SWIFTPM_MODULE_BUNDLE;  | 
 | 527 | +    NSBundle * bundle = SWIFTPM_MODULE_BUNDLE;  | 
549 | 528 | #else  | 
550 |  | -        NSBundle * bundle = [NSBundle bundleForClass:[GGMLMetalClass class]];  | 
 | 529 | +    NSBundle * bundle = [NSBundle bundleForClass:[GGMLMetalClass class]];  | 
551 | 530 | #endif  | 
552 | 531 | 
 
  | 
553 |  | -        NSString * path_lib = [bundle pathForResource:@"default" ofType:@"metallib"];  | 
554 |  | -        if (path_lib == nil) {  | 
555 |  | -            // Try to find the resource in the directory where the current binary located.  | 
556 |  | -            NSString * current_binary = [[NSProcessInfo processInfo] arguments][0];  | 
557 |  | -            NSString * bin_dir = [current_binary stringByDeletingLastPathComponent];  | 
558 |  | -            NSString * default_metallib_path = [NSString pathWithComponents:@[bin_dir, @"default.metallib"]];  | 
559 |  | -            if ([[NSFileManager defaultManager] isReadableFileAtPath:default_metallib_path]) {  | 
560 |  | -                GGML_LOG_INFO("%s: found '%s'\n", __func__, [default_metallib_path UTF8String]);  | 
561 |  | -                NSDictionary * atts = [[NSFileManager defaultManager] attributesOfItemAtPath:default_metallib_path error:&error];  | 
562 |  | -                if (atts && atts[NSFileType] == NSFileTypeSymbolicLink) {  | 
563 |  | -                    // Optionally, if this is a symlink, try to resolve it.  | 
564 |  | -                    default_metallib_path = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:default_metallib_path error:&error];  | 
565 |  | -                    if (default_metallib_path && [default_metallib_path length] > 0 && ![[default_metallib_path substringToIndex:1] isEqualToString:@"/"]) {  | 
566 |  | -                        // It is a relative path, adding the binary directory as directory prefix.  | 
567 |  | -                        default_metallib_path = [NSString pathWithComponents:@[bin_dir, default_metallib_path]];  | 
568 |  | -                    }  | 
569 |  | -                    if (!default_metallib_path || ![[NSFileManager defaultManager] isReadableFileAtPath:default_metallib_path]) {  | 
570 |  | -                        // Link to the resource could not be resolved.  | 
571 |  | -                        default_metallib_path = nil;  | 
572 |  | -                    } else {  | 
573 |  | -                        GGML_LOG_INFO("%s: symlink resolved '%s'\n", __func__, [default_metallib_path UTF8String]);  | 
574 |  | -                    }  | 
 | 532 | +    NSString * path_lib = [bundle pathForResource:@"default" ofType:@"metallib"];  | 
 | 533 | +    if (path_lib == nil) {  | 
 | 534 | +        // Try to find the resource in the directory where the current binary located.  | 
 | 535 | +        NSString * current_binary = [[NSProcessInfo processInfo] arguments][0];  | 
 | 536 | +        NSString * bin_dir = [current_binary stringByDeletingLastPathComponent];  | 
 | 537 | +        NSString * default_metallib_path = [NSString pathWithComponents:@[bin_dir, @"default.metallib"]];  | 
 | 538 | +        if ([[NSFileManager defaultManager] isReadableFileAtPath:default_metallib_path]) {  | 
 | 539 | +            GGML_LOG_INFO("%s: found '%s'\n", __func__, [default_metallib_path UTF8String]);  | 
 | 540 | +            NSDictionary * atts = [[NSFileManager defaultManager] attributesOfItemAtPath:default_metallib_path error:&error];  | 
 | 541 | +            if (atts && atts[NSFileType] == NSFileTypeSymbolicLink) {  | 
 | 542 | +                // Optionally, if this is a symlink, try to resolve it.  | 
 | 543 | +                default_metallib_path = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:default_metallib_path error:&error];  | 
 | 544 | +                if (default_metallib_path && [default_metallib_path length] > 0 && ![[default_metallib_path substringToIndex:1] isEqualToString:@"/"]) {  | 
 | 545 | +                    // It is a relative path, adding the binary directory as directory prefix.  | 
 | 546 | +                    default_metallib_path = [NSString pathWithComponents:@[bin_dir, default_metallib_path]];  | 
 | 547 | +                }  | 
 | 548 | +                if (!default_metallib_path || ![[NSFileManager defaultManager] isReadableFileAtPath:default_metallib_path]) {  | 
 | 549 | +                    // Link to the resource could not be resolved.  | 
 | 550 | +                    default_metallib_path = nil;  | 
 | 551 | +                } else {  | 
 | 552 | +                    GGML_LOG_INFO("%s: symlink resolved '%s'\n", __func__, [default_metallib_path UTF8String]);  | 
575 | 553 |                 }  | 
576 |  | -            } else {  | 
577 |  | -                // The resource couldn't be found in the binary's directory.  | 
578 |  | -                default_metallib_path = nil;  | 
579 | 554 |             }  | 
580 |  | -            path_lib = default_metallib_path;  | 
 | 555 | +        } else {  | 
 | 556 | +            // The resource couldn't be found in the binary's directory.  | 
 | 557 | +            default_metallib_path = nil;  | 
581 | 558 |         }  | 
 | 559 | +        path_lib = default_metallib_path;  | 
 | 560 | +    }  | 
582 | 561 | 
 
  | 
583 |  | -        if (path_lib != nil) {  | 
584 |  | -            // pre-compiled library found  | 
585 |  | -            NSURL * libURL = [NSURL fileURLWithPath:path_lib];  | 
586 |  | -            GGML_LOG_INFO("%s: loading '%s'\n", __func__, [path_lib UTF8String]);  | 
 | 562 | +    if (path_lib != nil) {  | 
 | 563 | +        // pre-compiled library found  | 
 | 564 | +        NSURL * libURL = [NSURL fileURLWithPath:path_lib];  | 
 | 565 | +        GGML_LOG_INFO("%s: loading '%s'\n", __func__, [path_lib UTF8String]);  | 
587 | 566 | 
 
  | 
588 |  | -            metal_library = [device newLibraryWithURL:libURL error:&error];  | 
589 |  | -            if (error) {  | 
590 |  | -                GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);  | 
591 |  | -                return NULL;  | 
592 |  | -            }  | 
593 |  | -        } else {  | 
594 |  | -            GGML_LOG_INFO("%s: default.metallib not found, loading from source\n", __func__);  | 
 | 567 | +        metal_library = [device newLibraryWithURL:libURL error:&error];  | 
 | 568 | +        if (error) {  | 
 | 569 | +            GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);  | 
 | 570 | +            return NULL;  | 
 | 571 | +        }  | 
 | 572 | +    } else {  | 
 | 573 | +        GGML_LOG_INFO("%s: default.metallib not found, loading from source\n", __func__);  | 
595 | 574 | 
 
  | 
596 |  | -            NSString * path_source;  | 
597 |  | -            NSString * path_resource = [[NSProcessInfo processInfo].environment objectForKey:@"GGML_METAL_PATH_RESOURCES"];  | 
 | 575 | +        NSString * path_source;  | 
 | 576 | +        NSString * path_resource = [[NSProcessInfo processInfo].environment objectForKey:@"GGML_METAL_PATH_RESOURCES"];  | 
598 | 577 | 
 
  | 
599 |  | -            GGML_LOG_INFO("%s: GGML_METAL_PATH_RESOURCES = %s\n", __func__, path_resource ? [path_resource UTF8String] : "nil");  | 
 | 578 | +        GGML_LOG_INFO("%s: GGML_METAL_PATH_RESOURCES = %s\n", __func__, path_resource ? [path_resource UTF8String] : "nil");  | 
600 | 579 | 
 
  | 
601 |  | -            if (path_resource) {  | 
602 |  | -                path_source = [path_resource stringByAppendingPathComponent:@"ggml-metal.metal"];  | 
603 |  | -            } else {  | 
604 |  | -                path_source = [bundle pathForResource:@"ggml-metal" ofType:@"metal"];  | 
605 |  | -            }  | 
 | 580 | +        if (path_resource) {  | 
 | 581 | +            path_source = [path_resource stringByAppendingPathComponent:@"ggml-metal.metal"];  | 
 | 582 | +        } else {  | 
 | 583 | +            path_source = [bundle pathForResource:@"ggml-metal" ofType:@"metal"];  | 
 | 584 | +        }  | 
606 | 585 | 
 
  | 
607 |  | -            if (path_source == nil) {  | 
608 |  | -                GGML_LOG_WARN("%s: error: could not use bundle path to find ggml-metal.metal, falling back to trying cwd\n", __func__);  | 
609 |  | -                path_source = @"ggml-metal.metal";  | 
610 |  | -            }  | 
 | 586 | +        if (path_source == nil) {  | 
 | 587 | +            GGML_LOG_WARN("%s: error: could not use bundle path to find ggml-metal.metal, falling back to trying cwd\n", __func__);  | 
 | 588 | +            path_source = @"ggml-metal.metal";  | 
 | 589 | +        }  | 
611 | 590 | 
 
  | 
612 |  | -            GGML_LOG_INFO("%s: loading '%s'\n", __func__, [path_source UTF8String]);  | 
 | 591 | +        GGML_LOG_INFO("%s: loading '%s'\n", __func__, [path_source UTF8String]);  | 
613 | 592 | 
 
  | 
614 |  | -            src = [NSString stringWithContentsOfFile:path_source encoding:NSUTF8StringEncoding error:&error];  | 
615 |  | -            if (error) {  | 
616 |  | -                GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);  | 
617 |  | -                return NULL;  | 
618 |  | -            }  | 
 | 593 | +        src = [NSString stringWithContentsOfFile:path_source encoding:NSUTF8StringEncoding error:&error];  | 
 | 594 | +        if (error) {  | 
 | 595 | +            GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);  | 
 | 596 | +            return NULL;  | 
619 | 597 |         }  | 
 | 598 | +    }  | 
620 | 599 | #endif  | 
621 | 600 | 
 
  | 
622 |  | -        if (!metal_library) {  | 
623 |  | -            @autoreleasepool {  | 
624 |  | -                // dictionary of preprocessor macros  | 
625 |  | -                NSMutableDictionary * prep = [NSMutableDictionary dictionary];  | 
 | 601 | +    if (!metal_library) {  | 
 | 602 | +        @autoreleasepool {  | 
 | 603 | +            // dictionary of preprocessor macros  | 
 | 604 | +            NSMutableDictionary * prep = [NSMutableDictionary dictionary];  | 
626 | 605 | 
 
  | 
627 |  | -                if (ctx_dev->use_bfloat) {  | 
628 |  | -                    [prep setObject:@"1" forKey:@"GGML_METAL_USE_BF16"];  | 
629 |  | -                }  | 
 | 606 | +            if (use_bfloat) {  | 
 | 607 | +                [prep setObject:@"1" forKey:@"GGML_METAL_USE_BF16"];  | 
 | 608 | +            }  | 
630 | 609 | 
 
  | 
631 | 610 | #if GGML_METAL_EMBED_LIBRARY  | 
632 |  | -                [prep setObject:@"1" forKey:@"GGML_METAL_EMBED_LIBRARY"];  | 
 | 611 | +            [prep setObject:@"1" forKey:@"GGML_METAL_EMBED_LIBRARY"];  | 
633 | 612 | #endif  | 
634 | 613 | 
 
  | 
635 |  | -                MTLCompileOptions * options = [MTLCompileOptions new];  | 
636 |  | -                options.preprocessorMacros = prep;  | 
 | 614 | +            MTLCompileOptions * options = [MTLCompileOptions new];  | 
 | 615 | +            options.preprocessorMacros = prep;  | 
637 | 616 | 
 
  | 
638 |  | -                //[options setFastMathEnabled:false];  | 
 | 617 | +            //[options setFastMathEnabled:false];  | 
639 | 618 | 
 
  | 
640 |  | -                metal_library = [device newLibraryWithSource:src options:options error:&error];  | 
641 |  | -                if (error) {  | 
642 |  | -                    GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);  | 
643 |  | -                    return NULL;  | 
644 |  | -                }  | 
 | 619 | +            metal_library = [device newLibraryWithSource:src options:options error:&error];  | 
 | 620 | +            if (error) {  | 
 | 621 | +                GGML_LOG_ERROR("%s: error: %s\n", __func__, [[error description] UTF8String]);  | 
 | 622 | +                return NULL;  | 
 | 623 | +            }  | 
645 | 624 | 
 
  | 
646 | 625 | #if !__has_feature(objc_arc)  | 
647 |  | -                [options release];  | 
 | 626 | +            [options release];  | 
648 | 627 | #endif  | 
649 |  | -            }  | 
650 | 628 |         }  | 
 | 629 | +    }  | 
651 | 630 | 
 
  | 
652 | 631 | #if GGML_METAL_EMBED_LIBRARY  | 
653 |  | -        [src release];  | 
 | 632 | +    [src release];  | 
654 | 633 | #endif // GGML_METAL_EMBED_LIBRARY  | 
 | 634 | + | 
 | 635 | +    return metal_library;  | 
 | 636 | +}  | 
 | 637 | + | 
 | 638 | +static struct ggml_backend_metal_context * ggml_metal_init(ggml_backend_dev_t dev) {  | 
 | 639 | +    GGML_LOG_INFO("%s: allocating\n", __func__);  | 
 | 640 | + | 
 | 641 | +#if TARGET_OS_OSX && !GGML_METAL_NDEBUG  | 
 | 642 | +    // Show all the Metal device instances in the system  | 
 | 643 | +    NSArray * devices = MTLCopyAllDevices();  | 
 | 644 | +    for (id<MTLDevice> device in devices) {  | 
 | 645 | +        GGML_LOG_INFO("%s: found device: %s\n", __func__, [[device name] UTF8String]);  | 
 | 646 | +    }  | 
 | 647 | +    [devices release]; // since it was created by a *Copy* C method  | 
 | 648 | +#endif  | 
 | 649 | + | 
 | 650 | +    // init context  | 
 | 651 | +    struct ggml_backend_metal_context * ctx = calloc(1, sizeof(struct ggml_backend_metal_context));  | 
 | 652 | +    struct ggml_backend_metal_device_context * ctx_dev = dev->context;  | 
 | 653 | + | 
 | 654 | +    id<MTLDevice> device = ggml_backend_metal_device_acq(ctx_dev);  | 
 | 655 | +    GGML_LOG_INFO("%s: picking default device: %s\n", __func__, [[device name] UTF8String]);  | 
 | 656 | + | 
 | 657 | +    ctx->queue  = [device newCommandQueue];  | 
 | 658 | +    if (ctx->queue == nil) {  | 
 | 659 | +        GGML_LOG_ERROR("%s: error: failed to create command queue\n", __func__);  | 
 | 660 | +        return NULL;  | 
 | 661 | +    }  | 
 | 662 | + | 
 | 663 | +    ctx->d_queue = dispatch_queue_create("ggml-metal", DISPATCH_QUEUE_CONCURRENT);  | 
 | 664 | + | 
 | 665 | +    // load library  | 
 | 666 | +    if (ctx_dev->mtl_library == nil) {  | 
 | 667 | +        ctx_dev->mtl_library = ggml_metal_load_library(device, ctx_dev->use_bfloat);  | 
 | 668 | +    }  | 
 | 669 | +    id<MTLLibrary> metal_library = ctx_dev->mtl_library;  | 
 | 670 | +    if (metal_library == nil) {  | 
 | 671 | +        GGML_LOG_ERROR("%s: error: metal library is nil\n", __func__);  | 
 | 672 | +        return NULL;  | 
655 | 673 |     }  | 
656 | 674 | 
 
  | 
657 | 675 |     // print MTL GPU family:  | 
@@ -725,7 +743,6 @@ @implementation GGMLMetalClass  | 
725 | 743 |             [metal_function release]; \  | 
726 | 744 |             if (error) { \  | 
727 | 745 |                 GGML_LOG_ERROR("%s: error: load pipeline error: %s\n", __func__, [[error description] UTF8String]); \  | 
728 |  | -                [metal_library release]; \  | 
729 | 746 |                 return NULL; \  | 
730 | 747 |             } \  | 
731 | 748 |         } else { \  | 
@@ -1044,8 +1061,6 @@ @implementation GGMLMetalClass  | 
1044 | 1061 |         GGML_METAL_ADD_KERNEL(GGML_METAL_KERNEL_TYPE_POOL_2D_MAX_F32,               pool_2d_max_f32,                true);  | 
1045 | 1062 |     }  | 
1046 | 1063 | 
 
  | 
1047 |  | -    [metal_library release];  | 
1048 |  | - | 
1049 | 1064 |     return ctx;  | 
1050 | 1065 | }  | 
1051 | 1066 | 
 
  | 
 | 
0 commit comments