char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
a2878d30d
char/aliceai-t5-moe
1#import "ggml-metal-context.h" 2 3#import "ggml-impl.h" 4#import "ggml-backend-impl.h" 5 6#import "ggml-metal-impl.h" 7#import "ggml-metal-common.h" 8#import "ggml-metal-ops.h" 9#import "ggml-metal-fusion.h" 10 11#import <Foundation/Foundation.h> 12 13#import <Metal/Metal.h> 14 15#undef MIN 16#undef MAX 17#define MIN(a, b) ((a) < (b) ? (a) : (b)) 18#define MAX(a, b) ((a) > (b) ? (a) : (b)) 19 20// max number of MTLCommandBuffer used to submit a graph for processing 21# define GGML_METAL_MAX_COMMAND_BUFFERS 8 22 23struct ggml_metal_command_buffer { 24id < MTLCommandBuffer > obj ; 25}; 26 27struct ggml_metal { 28char name[128]; 29 30ggml_metal_device_t dev; 31ggml_metal_library_t lib; 32 33ggml_metal_event_t ev_cpy; // for async copies 34 35d ispatch_queue_t d_queue; 36 37// additional, inference - time compiled pipelines 38ggml_metal_pipelines_t pipelines_ext; 39 40bool use_concurrency; 41bool use_graph_optimize; 42 43int debug_graph; 44 45struct ggml_metal_fusion_info * finfo; 46 47// capture state 48int capture_compute; 49bool capture_started; 50 51id < MTLCaptureScope > capture_scope ; 52 53// command buffer state 54int n_cb; // number of extra threads used to submit the command buffers 55int n_nodes_0; // number of nodes submitted by the main thread 56int n_nodes_1; // remaining number of nodes submitted by the n_cb threads 57int n_nodes_per_cb; 58 59struct ggml_cgraph * gf; 60 61// the callback given to the thread pool 62void ( ^ encode_async )( size_t ith); 63 64// n_cb command buffers + 1 used by the main thread 65struct ggml_metal_command_buffer cmd_bufs[GGML_METAL_MAX_COMMAND_BUFFERS + 1]; 66 67// extra command buffers for things like getting, setting and copying tensors 68NSMutableArray * cmd_bufs_ext ; 69 70// buffers to release after async Metal operations complete 71// if Metal released them, it would do so on a Metal-internal thread without an autorelease pool, which could cause leaks 72NSMutableArray * buf_refs ; 73 74// the last command buffer queued into the Metal queue with operations relevant to the current Metal backend 75id < MTLCommandBuffer > cmd_buf_last ; 76 77// abort ggml_metal_graph_compute if callback returns true 78ggml_abort_callback abort_callback; 79void * abort_callback_data ; 80 81// error state - set when a command buffer fails during synchronize 82// once set, graph_compute will return GGML_STATUS_FAILED until the backend is recreated 83bool has_error; 84}; 85 86ggml_metal_t ggml_metal_init(ggml_metal_device_t dev) { 87GGML_LOG_INFO ( "%s: allocating\n" , __func__ ); 88 89@ autoreleasepool { 90# if TARGET_OS_OSX && ! GGML_METAL_NDEBUG 91// Show all the Metal device instances in the system 92NSArray * devices = MTLCopyAllDevices(); 93for ( id <MTLDevice> device in devices) { 94GGML_LOG_INFO ( "%s: found device: %s\n" , __func__ , [[ device name] UTF8String]); 95} 96[ devices release]; // since it was created by a *Copy* C method 97# endif 98 99// init context 100ggml_metal_t res = calloc(1, sizeof(struct ggml_metal)); 101 102id < MTLDevice > device = ggml_metal_device_get_obj( dev ); 103 104GGML_LOG_INFO ( "%s: picking default device: %s\n" , __func__ , [[ device name] UTF8String]); 105 106// TODO : would it be better to have one queue for the backend and one queue for the device? 107// the graph encoders and async ops would use the backend queue while the sync ops would use the device queue? 108// res - > queue = [ device newCommandQueue]; [ TAG_QUEUE_PER_BACKEND ] 109id < MTLCommandQueue > queue = ggml_metal_device_get_queue( dev ); 110if ( queue == nil ) { 111GGML_LOG_ERROR ( "%s: error: failed to create command queue\n" , __func__ ); 112free ( res ); 113return NULL; 114} 115 116res - > dev = dev; 117res - > lib = ggml_metal_device_get_library( dev ); 118if ( res - > lib == NULL ) { 119GGML_LOG_WARN ( "%s: the device does not have a precompiled Metal library - this is unexpected\n" , __func__ ); 120GGML_LOG_WARN ( "%s: will try to compile it on the fly\n" , __func__ ); 121 122res - > lib = ggml_metal_library_init( dev ); 123if ( res - > lib == NULL ) { 124GGML_LOG_ERROR ( "%s: error: failed to initialize the Metal library\n" , __func__ ); 125 126free ( res ); 127 128return NULL; 129} 130} 131 132res - > ev_cpy = ggml_metal_device_event_init( dev ); 133 134const struct ggml_metal_device_props * props_dev = ggml_metal_device_get_props(dev); 135 136snprintf ( res - > name , sizeof ( res - > name ), "%s" , props_dev - > name ); 137 138res - > d_queue = dispatch_queue_create( "ggml-metal" , DISPATCH_QUEUE_CONCURRENT ); 139 140res - > use_concurrency = getenv( "GGML_METAL_CONCURRENCY_DISABLE" ) == nil ; 141 142{ 143const char * val = getenv( "GGML_METAL_GRAPH_DEBUG" ); 144res - > debug_graph = val ? atoi ( val ) : 0 ; 145} 146 147res - > use_graph_optimize = true; 148 149if ( getenv ( "GGML_METAL_GRAPH_OPTIMIZE_DISABLE" ) ! = NULL) { 150res - > use_graph_optimize = false; 151} 152 153res - > finfo = ggml_metal_device_get_fusion_info( dev ); 154if ( ggml_metal_fusion_info_stats ( res - > finfo )) { 155ggml_metal_fusion_info_labels_init ( res - > finfo ); 156res - > n_cb = 0; 157} 158 159GGML_LOG_INFO ( "%s: use fusion = %s\n" , __func__ , ggml_metal_fusion_info_enabled ( res - > finfo ) ? "true" : "false" ); 160GGML_LOG_INFO ( "%s: use concurrency = %s\n" , __func__ , res - > use_concurrency ? "true" : "false" ); 161GGML_LOG_INFO ( "%s: use graph optimize = %s\n" , __func__ , res - > use_graph_optimize ? "true" : "false" ); 162 163res - > capture_compute = 0; 164res - > capture_started = false; 165res - > capture_scope = nil; 166 167{ 168const char * val = getenv( "GGML_METAL_CAPTURE_COMPUTE" ); 169if ( val ) { 170res - > capture_compute = atoi( val ); 171} 172} 173 174res - > has_error = false; 175 176res - > gf = nil; 177res - > encode_async = nil; 178for ( int i = 0 ; i < GGML_METAL_MAX_COMMAND_BUFFERS ; ++ i ) { 179res - > cmd_bufs [ i ].obj = nil; 180} 181 182res - > cmd_bufs_ext = [[ NSMutableArray alloc] init]; 183res - > buf_refs = [[ NSMutableArray alloc] init]; 184 185res - > cmd_buf_last = nil; 186 187res - > pipelines_ext = ggml_metal_pipelines_init(); 188 189return res; 190} 191} 192 193v oid ggml_metal_free(ggml_metal_t ctx) { 194GGML_LOG_INFO ( "%s: deallocating\n" , __func__ ); 195 196for ( int i = 0 ; i < GGML_METAL_MAX_COMMAND_BUFFERS ; ++ i ) { 197if ( ctx - > cmd_bufs [ i ]. obj ) { 198[ ctx - > cmd_bufs [ i ].obj release]; 199} 200} 201 202for ( int i = 0 ; i < ( int ) ctx - > cmd_bufs_ext . count ; ++ i ) { 203if ( ctx - > cmd_bufs_ext [ i ]) { 204[ ctx - > cmd_bufs_ext [ i ] release]; 205} 206} 207 208[ ctx - > cmd_bufs_ext removeAllObjects]; 209[ ctx - > cmd_bufs_ext release]; 210 211@ autoreleasepool { 212[ ctx - > buf_refs removeAllObjects]; 213[ ctx - > buf_refs release]; 214} 215 216if ( ctx - > pipelines_ext ) { 217ggml_metal_pipelines_free ( ctx - > pipelines_ext ); 218ctx - > pipelines_ext = nil; 219} 220 221if ( ggml_metal_fusion_info_debug ( ctx - > finfo ) > 0 ) { 222GGML_LOG_DEBUG ( "%s: fusion stats:\n" , __func__ ); 223 224const int n_fusions = ggml_metal_fusion_info_n_fusions( ctx - > finfo ); 225for ( int i = 0; i < n_fusions ; i ++ ) { 226const uint64_t count = ggml_metal_fusion_info_count( ctx - > finfo , i ); 227if ( count == 0 ) { 228continue; 229} 230 231// note : cannot use ggml_log here 232GGML_LOG_DEBUG ( "%s: - %s: %" PRIu64 "\n", __func__ , ggml_metal_fusion_info_label ( ctx - > finfo , i ), count ); 233} 234} 235 236Block_release ( ctx - > encode_async ); 237 238// [ ctx - > queue release]; // [ TAG_QUEUE_PER_BACKEND ] 239 240dispatch_release ( ctx - > d_queue ); 241 242ggml_metal_device_event_free ( ctx - > dev , ctx - > ev_cpy ); 243 244free ( ctx ); 245} 246 247con st char * ggml_metal_get_name(ggml_metal_t ctx) { 248return ctx - >name; 249} 250 251v oid ggml_metal_synchronize(ggml_metal_t ctx) { 252// wait for any backend operations to finish 253if ( ctx - > cmd_buf_last ) { 254[ ctx - > cmd_buf_last waitUntilCompleted]; 255ctx - > cmd_buf_last = nil; 256} 257 258// check status of all command buffers 259{ 260const int n_cb = ctx - > n_cb ; 261 262for ( int cb_idx = 0; cb_idx <= n_cb ; ++ cb_idx ) { 263id < MTLCommandBuffer > cmd_buf = ctx - > cmd_bufs [ cb_idx ]. obj ; 264if (! cmd_buf ) { 265continue; 266} 267 268MTLCommandBufferStatus status = [ cmd_buf status]; 269if ( status ! = MTLCommandBufferStatusCompleted) { 270GGML_LOG_ERROR ( "%s: error: command buffer %d failed with status %d\n" , __func__ , cb_idx , ( int ) status); 271if ( status == MTLCommandBufferStatusError ) { 272GGML_LOG_ERROR ( "error: %s\n" , [[ cmd_buf error].localizedDescription UTF8String]); 273} 274ctx - > has_error = true; 275return; 276} 277} 278} 279 280// release any completed extra command buffers 281if ( ctx - > cmd_bufs_ext . count > 0 ) { 282for ( size_t i = 0 ; i < ctx - > cmd_bufs_ext . count ; ++ i ) { 283id < MTLCommandBuffer > cmd_buf = ctx - > cmd_bufs_ext [ i ]; 284 285MTLCommandBufferStatus status = [ cmd_buf status]; 286if ( status ! = MTLCommandBufferStatusCompleted) { 287GGML_LOG_ERROR ( "%s: error: command buffer %d failed with status %d\n" , __func__ , ( int ) i, ( int ) status); 288if ( status == MTLCommandBufferStatusError ) { 289GGML_LOG_ERROR ( "error: %s\n" , [[ cmd_buf error].localizedDescription UTF8String]); 290} 291 292// release this and all remaining command buffers before returning 293for ( size_t j = i; j < ctx - > cmd_bufs_ext . count ; ++ j ) { 294[ ctx - > cmd_bufs_ext [ j ] release]; 295} 296[ ctx - > cmd_bufs_ext removeAllObjects]; 297 298ctx - > has_error = true; 299return; 300} 301 302[ cmd_buf release]; 303} 304 305[ ctx - > cmd_bufs_ext removeAllObjects]; 306} 307 308@ autoreleasepool { 309[ ctx - > buf_refs removeAllObjects]; 310} 311} 312 313s tatic struct ggml_metal_buffer_id ggml_metal_get_buffer_id(const struct ggml_tensor * t) { 314if (! t ) { 315return ( struct ggml_metal_buffer_id) { nil , 0 }; 316} 317 318ggml_backend_buffer_t buffer = t->view_src ? t->view_src->buffer : t->buffer; 319 320return ggml_metal_buffer_get_id( buffer - > context , t ); 321} 322 323v oid ggml_metal_set_tensor_async(ggml_metal_t ctx, struct ggml_tensor * tensor, const void * data, size_t offset, size_t size) { 324@ autoreleasepool { 325// wrap the source data into a Metal buffer 326id < MTLDevice > device = ggml_metal_device_get_obj( ctx - > dev ); 327id < MTLBuffer > buf_src = [ device newBufferWithBytes : data 328length : size 329options : MTLResourceStorageModeShared ]; 330 331GGML_ASSERT ( buf_src ); 332 333struct ggml_metal_buffer_id bid_dst = ggml_metal_get_buffer_id(tensor); 334if ( bid_dst . metal == nil ) { 335GGML_ABORT ( "%s: failed to find buffer for tensor '%s'\n" , __func__ , tensor - > name ); 336} 337 338bid_dst . offs += offset; 339 340// queue the copy operation into the queue of the Metal context 341// this will be queued at the end, after any currently ongoing GPU operations 342id < MTLCommandQueue > queue = ggml_metal_device_get_queue( ctx - > dev ); 343id < MTLCommandBuffer > cmd_buf = [ queue commandBuffer]; 344id < MTLBlitCommandEncoder > encoder = [ cmd_buf blitCommandEncoder]; 345 346[ encoder copyFromBuffer : buf_src 347sourceOffset : 0 348toBuffer : bid_dst . metal 349destinationOffset : bid_dst . offs 350size : size ]; 351 352[ encoder endEncoding]; 353[ cmd_buf commit]; 354 355[ ctx - > buf_refs addObject : buf_src ]; 356[ buf_src release]; 357 358// do not wait here for completion 359// [ cmd_buf waitUntilCompleted]; 360 361// instead, remember a reference to the command buffer and wait for it later if needed 362[ ctx - > cmd_bufs_ext addObject : cmd_buf ]; 363ctx - > cmd_buf_last = cmd_buf; 364 365[ cmd_buf retain]; 366} 367} 368 369v oid ggml_metal_get_tensor_async(ggml_metal_t ctx, const struct ggml_tensor * tensor, void * data, size_t offset, size_t size) { 370@ autoreleasepool { 371id < MTLDevice > device = ggml_metal_device_get_obj( ctx - > dev ); 372id < MTLBuffer > buf_dst = [ device newBufferWithBytesNoCopy : data 373length : size 374options : MTLResourceStorageModeShared 375deallocator : nil ]; 376 377GGML_ASSERT ( buf_dst ); 378 379struct ggml_metal_buffer_id bid_src = ggml_metal_get_buffer_id( tensor ); 380if ( bid_src . metal == nil ) { 381GGML_ABORT ( "%s: failed to find buffer for tensor '%s'\n" , __func__ , tensor - > name ); 382} 383 384bid_src . offs += offset; 385 386// queue the copy operation into the queue of the Metal context 387// this will be queued at the end, after any currently ongoing GPU operations 388id < MTLCommandQueue > queue = ggml_metal_device_get_queue( ctx - > dev ); 389id < MTLCommandBuffer > cmd_buf = [ queue commandBuffer]; 390id < MTLBlitCommandEncoder > encoder = [ cmd_buf blitCommandEncoder]; 391 392[ encoder copyFromBuffer : bid_src . metal 393sourceOffset : bid_src . offs 394toBuffer : buf_dst 395destinationOffset : 0 396size : size ]; 397 398[ encoder endEncoding]; 399[ cmd_buf commit]; 400 401[ ctx - > buf_refs addObject : buf_dst ]; 402[ buf_dst release]; 403 404// do not wait here for completion 405// [ cmd_buf waitUntilCompleted]; 406 407// instead, remember a reference to the command buffer and wait for it later if needed 408[ ctx - > cmd_bufs_ext addObject : cmd_buf ]; 409ctx - > cmd_buf_last = cmd_buf; 410 411[ cmd_buf retain]; 412} 413} 414 415b ool ggml_metal_cpy_tensor_async(ggml_metal_t ctx_src, ggml_metal_t ctx_dst, const struct ggml_tensor * src, struct ggml_tensor * dst) { 416@ autoreleasepool { 417struct ggml_metal_buffer_id bid_src = ggml_metal_get_buffer_id( src ); 418struct ggml_metal_buffer_id bid_dst = ggml_metal_get_buffer_id( dst ); 419 420if ( bid_src . metal == nil || bid_dst . metal == nil ) { 421return false; 422} 423 424// queue the copy operation into the Metal context 425// this will be queued at the end, after any currently ongoing GPU operations 426id < MTLCommandQueue > queue = ggml_metal_device_get_queue( ctx_src - > dev ); 427id < MTLCommandBuffer > cmd_buf = [ queue commandBuffer]; 428id < MTLBlitCommandEncoder > encoder = [ cmd_buf blitCommandEncoder]; 429 430[ encoder copyFromBuffer : bid_src . metal 431sourceOffset : bid_src . offs 432toBuffer : bid_dst . metal 433destinationOffset : bid_dst . offs 434size : ggml_nbytes ( src )]; 435 436[ encoder endEncoding]; 437 438ggml_metal_event_t ev_cpy = ggml_metal_get_ev_cpy( ctx_src ); 439ggml_metal_event_encode_signal ( ev_cpy , cmd_buf ); 440 441[ cmd_buf commit]; 442 443// do not wait here for completion 444// [ cmd_buf waitUntilCompleted]; 445 446// instead, remember a reference to the command buffer and wait for it later if needed 447[ ctx_src - > cmd_bufs_ext addObject : cmd_buf ]; 448ctx_src - > cmd_buf_last = cmd_buf; 449 450[ cmd_buf retain]; 451 452ggml_metal_event_wait ( ctx_dst , ev_cpy ); 453 454return true; 455} 456} 457 458enum ggml_status ggml_metal_graph_compute(ggml_metal_t ctx, struct ggml_cgraph * gf) { 459if ( ctx - > has_error ) { 460GGML_LOG_ERROR ( "%s: backend is in error state from a previous command buffer failure - recreate the backend to recover\n" , __func__ ); 461return GGML_STATUS_FAILED; 462} 463 464// number of nodes encoded by the main thread (empirically determined) 465const int n_main = MAX(64, 0.1*gf->n_nodes); 466 467// number of threads in addition to the main thread 468const int n_cb = ctx->n_cb; 469 470// keep the memory wired 471ggml_metal_device_rsets_keep_alive ( ctx - > dev ); 472 473// submit the ggml compute graph to the GPU by creating command buffers and encoding the ops in them 474// the first n_nodes_0 are encoded and submitted for processing directly by the calling thread 475// while these nodes are processing, we start n_cb threads to enqueue the rest of the nodes 476// each thread creates it's own command buffer and enqueues the ops in parallel 477// 478// tests on M1 Pro and M2 Ultra using LLaMA models, show that optimal values for n_cb are 1 or 2 479 480@ autoreleasepool { 481ctx - > gf = gf; 482 483if ( ctx - > n_cb == 0 ) { 484// single - threaded encoding : the whole graph is encoded by one command buffer 485ctx - > n_nodes_0 = gf - > n_nodes ; 486ctx - > n_nodes_1 = 0; 487ctx - > n_nodes_per_cb = 0; 488} else { 489ctx - > n_nodes_0 = MIN( n_main , gf - > n_nodes ); 490ctx - > n_nodes_1 = gf - > n_nodes - ctx - > n_nodes_0 ; 491 492ctx - > n_nodes_per_cb = ( ctx - > n_nodes_1 + ctx - > n_cb - 1 ) / ctx - > n_cb ; 493} 494 495if ( ctx - > capture_compute >= 0 ) { 496ctx - > capture_compute -- ; 497} 498 499const bool use_capture = ctx - > capture_compute == 0 ; 500if ( use_capture ) { 501ctx - > capture_compute = - 1 ; 502 503// make sure all previous computations have finished before starting the capture 504if ( ctx - > cmd_buf_last ) { 505[ ctx - > cmd_buf_last waitUntilCompleted]; 506ctx - > cmd_buf_last = nil; 507} 508 509if (! ctx - > capture_started ) { 510NSString * path = [ NSString stringWithFormat :@ "/tmp/perf-metal-%d.gputrace" , getpid ()]; 511 512GGML_LOG_WARN ( "%s: capturing graph in %s\n" , __func__ , [ path UTF8String]); 513 514// create capture scope 515id < MTLDevice > device = ggml_metal_device_get_obj( ctx - > dev ); 516ctx - > capture_scope = [[ MTLCaptureManager sharedCaptureManager] newCaptureScopeWithDevice : device ]; 517 518MTLCaptureDescriptor * descriptor = [ MTLCaptureDescriptor new]; 519descriptor . captureObject = ctx - > capture_scope ; 520descriptor . destination = MTLCaptureDestinationGPUTraceDocument; 521descriptor . outputURL = [ NSURL fileURLWithPath : path ]; 522 523NSError * error = nil; 524if (![[ MTLCaptureManager sharedCaptureManager] startCaptureWithDescriptor : descriptor error : & error ]) { 525GGML_LOG_ERROR ( "%s: error: unable to start capture '%s'\n" , __func__ , [[ error localizedDescription] UTF8String]); 526} else { 527[ ctx - > capture_scope beginScope]; 528ctx - > capture_started = true; 529} 530} 531} 532 533// short - hand 534id < MTLCommandQueue > queue = ggml_metal_device_get_queue( ctx - > dev ); 535 536// the main thread commits the first few commands immediately 537// cmd_buf[ n_cb ] 538{ 539id < MTLCommandBuffer > cmd_buf = [ queue commandBufferWithUnretainedReferences]; 540[ cmd_buf retain]; 541 542if ( ctx - > cmd_bufs [ n_cb ]. obj ) { 543[ ctx - > cmd_bufs [ n_cb ].obj release]; 544} 545ctx - > cmd_bufs [ n_cb ].obj = cmd_buf; 546 547[ cmd_buf enqueue]; 548 549ctx - > encode_async ( n_cb ); 550} 551 552// remember the command buffer for the next iteration 553ctx - > cmd_buf_last = ctx - > cmd_bufs [ n_cb ]. obj ; 554 555// prepare the rest of the command buffers asynchronously ( optional ) 556// cmd_buf[ 0. . n_cb) 557for ( int cb_idx = 0; cb_idx < n_cb ; ++ cb_idx ) { 558id < MTLCommandBuffer > cmd_buf = [ queue commandBufferWithUnretainedReferences]; 559[ cmd_buf retain]; 560 561if ( ctx - > cmd_bufs [ cb_idx ]. obj ) { 562[ ctx - > cmd_bufs [ cb_idx ].obj release]; 563} 564ctx - > cmd_bufs [ cb_idx ].obj = cmd_buf; 565 566// always enqueue the first two command buffers 567// enqueue all of the command buffers if we don ' t need to abort 568if ( cb_idx < 2 || ctx - > abort_callback == NULL ) { 569[ cmd_buf enqueue]; 570 571// update the pointer to the last queued command buffer 572// this is needed to implement synchronize() 573ctx - > cmd_buf_last = cmd_buf; 574} 575} 576 577dispatch_apply ( n_cb , ctx - > d_queue , ctx - > encode_async ); 578 579// for debugging : block until graph is computed 580// [ ctx - > cmd_buf_last waitUntilCompleted]; 581 582// enter here only when capturing in order to wait for all computation to finish 583// otherwise, we leave the graph to compute asynchronously 584if ( use_capture && ctx - > capture_started ) { 585// wait for completion and check status of each command buffer 586// needed to detect if the device ran out - of - memory for example (# 1881 ) 587{ 588id < MTLCommandBuffer > cmd_buf = ctx - > cmd_bufs [ n_cb ]. obj ; 589[ cmd_buf waitUntilCompleted]; 590 591MTLCommandBufferStatus status = [ cmd_buf status]; 592if ( status ! = MTLCommandBufferStatusCompleted) { 593GGML_LOG_INFO ( "%s: command buffer %d failed with status %lu\n" , __func__ , n_cb , status ); 594if ( status == MTLCommandBufferStatusError ) { 595GGML_LOG_INFO ( "error: %s\n" , [[ cmd_buf error].localizedDescription UTF8String]); 596} 597 598return GGML_STATUS_FAILED; 599} 600} 601 602for ( int i = 0; i < n_cb ; ++ i ) { 603id < MTLCommandBuffer > cmd_buf = ctx - > cmd_bufs [ i ]. obj ; 604[ cmd_buf waitUntilCompleted]; 605 606MTLCommandBufferStatus status = [ cmd_buf status]; 607if ( status ! = MTLCommandBufferStatusCompleted) { 608GGML_LOG_INFO ( "%s: command buffer %d failed with status %lu\n" , __func__ , i , status ); 609if ( status == MTLCommandBufferStatusError ) { 610GGML_LOG_INFO ( "error: %s\n" , [[ cmd_buf error].localizedDescription UTF8String]); 611} 612 613return GGML_STATUS_FAILED; 614} 615 616id < MTLCommandBuffer > next_buffer = ( i + 1 < n_cb ? ctx - > cmd_bufs [ i + 1 ].obj : nil ); 617if (! next_buffer ) { 618continue; 619} 620 621const bool next_queued = ([ next_buffer status] ! = MTLCommandBufferStatusNotEnqueued); 622if ( next_queued ) { 623continue; 624} 625 626if ( ctx - > abort_callback && ctx - > abort_callback ( ctx - > abort_callback_data )) { 627GGML_LOG_INFO ( "%s: command buffer %d aborted" , __func__ , i ); 628return GGML_STATUS_ABORTED; 629} 630 631[ next_buffer commit]; 632} 633 634[ ctx - > capture_scope endScope]; 635[[ MTLCaptureManager sharedCaptureManager] stopCapture]; 636 637ctx - > capture_started = false; 638} 639} 640 641return GGML_STATUS_SUCCESS; 642} 643 644v oid ggml_metal_graph_optimize(ggml_metal_t ctx, struct ggml_cgraph * gf) { 645// const int64_t t_start = ggml_time_us(); 646 647if ( ctx - > use_graph_optimize ) { 648ggml_graph_optimize ( gf ); 649} 650 651// printf ( "%s: graph optimize took %.3f ms\n" , __func__ , ( ggml_time_us () - t_start ) / 1000.0 ); 652} 653 654v oid ggml_metal_event_record(ggml_metal_t ctx, ggml_metal_event_t ev) { 655@ autoreleasepool { 656id < MTLCommandQueue > queue = ggml_metal_device_get_queue( ctx - > dev ); 657id < MTLCommandBuffer > cmd_buf = [ queue commandBuffer]; 658 659ggml_metal_event_encode_signal ( ev , cmd_buf ); 660 661[ cmd_buf commit]; 662 663[ ctx - > cmd_bufs_ext addObject : cmd_buf ]; 664ctx - > cmd_buf_last = cmd_buf; 665 666[ cmd_buf retain]; 667} 668} 669 670v oid ggml_metal_event_wait(ggml_metal_t ctx, ggml_metal_event_t ev) { 671@ autoreleasepool { 672id < MTLCommandQueue > queue = ggml_metal_device_get_queue( ctx - > dev ); 673id < MTLCommandBuffer > cmd_buf = [ queue commandBuffer]; 674 675ggml_metal_event_encode_wait ( ev , cmd_buf ); 676 677[ cmd_buf commit]; 678 679[ ctx - > cmd_bufs_ext addObject : cmd_buf ]; 680ctx - > cmd_buf_last = cmd_buf; 681 682[ cmd_buf retain]; 683} 684} 685 686g gml_metal_event_t ggml_metal_get_ev_cpy(ggml_metal_t ctx) { 687return ctx - >ev_cpy; 688} 689 690v oid ggml_metal_set_n_cb(ggml_metal_t ctx, int n_cb) { 691// when fusion stats are collected the graph must be encoded by a single thread so the 692// counters are race-free; override whatever the caller requested 693if ( ggml_metal_fusion_info_stats ( ctx - > finfo )) { 694n_cb = 0; 695} 696 697if ( ctx - > n_cb ! = n_cb) { 698ctx - > n_cb = MIN( n_cb , GGML_METAL_MAX_COMMAND_BUFFERS ); 699 700if ( ctx - > n_cb > 2 ) { 701GGML_LOG_WARN ( "%s: n_cb = %d, using n_cb > 2 is not recommended and can degrade the performance in some cases\n" , __func__ , n_cb ); 702} 703} 704 705if ( ctx - > encode_async ) { 706Block_release ( ctx - > encode_async ); 707} 708 709ctx - > encode_async = Block_copy( ^ ( size_t iter) { 710const int cb_idx = iter; 711const int n_cb_l = ctx - > n_cb ; 712 713const int n_nodes_0 = ctx - > n_nodes_0 ; 714const int n_nodes_1 = ctx - > n_nodes_1 ; 715 716const int n_nodes_per_cb = ctx - > n_nodes_per_cb ; 717 718int idx_start = 0; 719int idx_end = n_nodes_0; 720 721if ( cb_idx < n_cb_l ) { 722idx_start = n_nodes_0 + ( ( cb_idx + 0 ) * n_nodes_per_cb ); 723idx_end = n_nodes_0 + ( MIN (( cb_idx == n_cb_l - 1 ) ? n_nodes_1 : ( cb_idx + 1 ) * n_nodes_per_cb , n_nodes_1 )); 724} 725 726id < MTLCommandBuffer > cmd_buf = ctx - > cmd_bufs [ cb_idx ]. obj ; 727 728ggml_metal_op_t ctx_op = ggml_metal_op_init( 729c tx - > dev , 730c md_buf , 731c tx - > gf , 732c tx - > finfo , 733i dx_start , 734i dx_end , 735c tx - > use_concurrency , 736c tx - > capture_compute , 737c tx - > debug_graph ); 738 739for ( int idx = 0; idx < ggml_metal_op_n_nodes ( ctx_op ); ++ idx ) { 740const int res = ggml_metal_op_encode( ctx_op , idx ); 741if ( res == 0 ) { 742break; 743} 744 745idx += res - 1 ; 746} 747 748ggml_metal_op_free ( ctx_op ); 749 750if ( cb_idx < 2 || ctx - > abort_callback == NULL ) { 751[ cmd_buf commit]; 752} 753}); 754} 755 756v oid ggml_metal_set_abort_callback(ggml_metal_t ctx, ggml_abort_callback abort_callback, void * user_data) { 757ctx - > abort_callback = abort_callback; 758ctx - > abort_callback_data = user_data; 759} 760 761b ool ggml_metal_supports_family(ggml_metal_t ctx, int family) { 762GGML_ASSERT ( ctx - > dev ! = nil ); 763 764id < MTLDevice > device = ggml_metal_device_get_obj( ctx - > dev ); 765 766return [ device supportsFamily : ( MTLGPUFamilyApple1 + family - 1 )]; 767} 768 769v oid ggml_metal_capture_next_compute(ggml_metal_t ctx) { 770ctx - > capture_compute = 1; 771}