char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
a2878d30d
char/aliceai-t5-moe
1#include "ggml-metal-fusion.h" 2 3#include "ggml-backend-impl.h" 4#include "ggml-metal-device.h" 5 6#include <algorithm> 7#include <string> 8#include <vector> 9 10// ---- helpers ------------------------------------------------------------- 11 12// true if two tensors live in the same Metal buffer 13static bool ggml_metal_fusion_same_buffer (const ggml_tensor * a ,const ggml_tensor * b ) { 14if (!a || !b ) { 15return false; 16 } 17 18ggml_backend_buffer_t ba = a -> view_src ?a -> view_src -> buffer :a -> buffer ; 19ggml_backend_buffer_t bb = b -> view_src ?b -> view_src -> buffer :b -> buffer ; 20 21ggml_metal_buffer_t ca = (ggml_metal_buffer_t )ba -> context ; 22ggml_metal_buffer_t cb = (ggml_metal_buffer_t )bb -> context ; 23 24return ggml_metal_buffer_get_id (ca ,a ).metal == ggml_metal_buffer_get_id (cb ,b ).metal ; 25} 26 27// ---- pattern checks ------------------------------------------------------ 28 29// NORM/RMS_NORM + MUL + ADD: the weight/bias of each fused step must match the norm input 30// width, be contiguous rows, and the fused outputs must stay F32 31static bool ggml_metal_fusion_check_norm ( 32const ggml_metal_fusion * fusion , 33const ggml_tensor * const * nodes , 34ggml_metal_fusion_mode mode ) { 35GGML_UNUSED (mode ); 36 37GGML_ASSERT (fusion -> n_ops >=2 ); 38 39for (int j = 1 ;j < fusion -> n_ops ;j ++ ) { 40// the fused MUL/ADD must read the previous node as src0 41if (nodes [j ]-> src [0 ]!= nodes [j - 1 ]) { 42return false; 43 } 44 45// the weight/bias must have the same row width as the norm input 46if (nodes [j ]-> src [1 ]-> ne [0 ]!= nodes [0 ]-> ne [0 ]) { 47return false; 48 } 49 50if (!ggml_is_contiguous_rows (nodes [j ]-> src [1 ])) { 51return false; 52 } 53 54if (nodes [j ]-> type != GGML_TYPE_F32 ) { 55return false; 56 } 57 } 58 59return true; 60} 61 62// ADD x N: each ADD reads the previous ADD as src0, and all addends must share layout 63// (and, in FULL mode, live in the same Metal buffer) 64static bool ggml_metal_fusion_check_add_chain ( 65const ggml_metal_fusion * fusion , 66const ggml_tensor * const * nodes , 67ggml_metal_fusion_mode mode ) { 68GGML_ASSERT (fusion -> n_ops >=2 ); 69 70for (int j = 1 ;j < fusion -> n_ops ;j ++ ) { 71if (nodes [j ]-> src [0 ]!= nodes [j - 1 ]) { 72return false; 73 } 74 75if (!ggml_are_same_layout (nodes [j ]-> src [1 ],nodes [j - 1 ]-> src [1 ])) { 76return false; 77 } 78 79if (mode == GGML_METAL_FUSION_FULL ) { 80if (!ggml_metal_fusion_same_buffer (nodes [j ]-> src [1 ],nodes [0 ]-> src [1 ])) { 81return false; 82 } 83 } 84 } 85 86return true; 87} 88 89// GATED_DELTA_NET + CPY: the trailing cpy scatters the gdn state snapshots into the recurrent 90// cache, so the gdn kernel writes them straight to the cache and the cpy is elided. 91// mirrors ggml_metal_op_can_fuse_gdn_cache (PR #25788). the gdn output has other consumers (the 92// attn scores view), so unlike the other patterns this is not an elision chain: the structural 93// checks live entirely in this callback (unsafe = true). 94static bool ggml_metal_fusion_check_gdn_cache ( 95const ggml_metal_fusion * fusion , 96const ggml_tensor * const * nodes , 97ggml_metal_fusion_mode mode ) { 98GGML_UNUSED (fusion ); 99 100const ggml_tensor * gdn = nodes [0 ]; 101const ggml_tensor * cpy = nodes [1 ]; 102 103// the kernel skips the snapshot tail, so the gdn output must not be a graph output 104if (gdn -> type != GGML_TYPE_F32 || (gdn -> flags & GGML_TENSOR_FLAG_OUTPUT )) { 105return false; 106 } 107 108if (cpy -> op != GGML_OP_CPY || (cpy -> flags & GGML_TENSOR_FLAG_OUTPUT )) { 109return false; 110 } 111 112const int64_t S_v = gdn -> src [2 ]-> ne [0 ]; 113const int64_t H = gdn -> src [2 ]-> ne [1 ]; 114const int64_t n_tokens = gdn -> src [2 ]-> ne [2 ]; 115const int64_t n_seqs = gdn -> src [2 ]-> ne [3 ]; 116const int64_t K = ggml_get_op_params_i32 (gdn ,0 ); 117const size_t tail_off = ggml_row_size (GGML_TYPE_F32 ,S_v * H * n_tokens * n_seqs ); 118 119const int64_t D = S_v * S_v * H ; 120const int64_t n_written = std::min < int64_t > (n_tokens ,K ); 121 122const ggml_tensor * src = cpy -> src [0 ];// gdn snapshot tail view 123const ggml_tensor * dst = cpy -> src [1 ];// cache view 124 125// src must be this gdn's snapshot tail (contiguous, at the tail offset) 126if (src -> op != GGML_OP_VIEW || src -> view_src != gdn || 127src -> view_offs != tail_off || !ggml_is_contiguous (src )) { 128return false; 129 } 130 131const int64_t expected_ne [GGML_MAX_DIMS ]= {D ,n_seqs ,n_written ,1 }; 132if (dst -> type != GGML_TYPE_F32 || 133 !std::equal (expected_ne ,expected_ne + GGML_MAX_DIMS ,dst -> ne )|| 134dst -> nb [0 ]!= ggml_type_size (GGML_TYPE_F32 )|| 135dst -> nb [1 ]!= ggml_row_size (GGML_TYPE_F32 ,D )) { 136return false; 137 } 138 139if (mode == GGML_METAL_FUSION_FULL ) { 140// the cache must be allocated so the kernel can write straight to its buffer 141if (dst -> data == nullptr ) { 142return false; 143 } 144 } 145 146return true; 147} 148 149// MUL + SIN + SQR + MUL + ADD (snake activation) 150static bool ggml_metal_fusion_check_snake ( 151const ggml_metal_fusion * fusion , 152const ggml_tensor * const * nodes , 153ggml_metal_fusion_mode mode ) { 154GGML_UNUSED (fusion ); 155GGML_UNUSED (mode ); 156 157const ggml_tensor * mul0 = nodes [0 ]; 158const ggml_tensor * sin_node = nodes [1 ]; 159const ggml_tensor * sqr = nodes [2 ]; 160const ggml_tensor * mul1 = nodes [3 ]; 161const ggml_tensor * add = nodes [4 ]; 162 163// x carries the full activation shape, a is the broadcast operand 164const ggml_tensor * x = ggml_are_same_shape (mul0 ,mul0 -> src [0 ]) ?mul0 -> src [0 ] :mul0 -> src [1 ]; 165const ggml_tensor * a = (x == mul0 -> src [0 ]) ?mul0 -> src [1 ] :mul0 -> src [0 ]; 166 167// mul1 reads sqr and inv_b in either operand order 168const ggml_tensor * inv_b = (mul1 -> src [0 ]== sqr ) ?mul1 -> src [1 ] :mul1 -> src [0 ]; 169 170// closure check: the trailing add reads the same x as the leading mul 171const ggml_tensor * x_in_add = (add -> src [0 ]== mul1 ) ?add -> src [1 ] :add -> src [0 ]; 172 173// x is in the supported whitelist and every chain intermediate shares x's type. 174// a and inv_b bind as device const float * in the kernel, so they stay F32. 175const bool types_ok = 176 (x -> type == GGML_TYPE_F32 || x -> type == GGML_TYPE_F16 || x -> type == GGML_TYPE_BF16 )&& 177 (a -> type == GGML_TYPE_F32 )&& (inv_b -> type == GGML_TYPE_F32 )&& 178 (mul0 -> type == x -> type )&& (sin_node -> type == x -> type )&& 179 (sqr -> type == x -> type )&& (mul1 -> type == x -> type )&& 180 (add -> type == x -> type ); 181 182// a / inv_b collapse to [1, C, 1, 1], x and add stay 2D 183const bool shape_ok = ggml_are_same_shape (a ,inv_b )&& a -> ne [0 ]== 1 && a -> ne [1 ]== x -> ne [1 ]; 184const bool dim_ok = 185 (x -> ne [2 ]== 1 )&& (x -> ne [3 ]== 1 )&& 186 (add -> ne [2 ]== 1 )&& (add -> ne [3 ]== 1 )&& 187 (a -> ne [2 ]== 1 )&& (a -> ne [3 ]== 1 )&& 188 (inv_b -> ne [2 ]== 1 )&& (inv_b -> ne [3 ]== 1 ); 189 190// kernel reads x[idx] and a[c] / inv_b[c] linearly, so every operand is contiguous 191const bool contig_ok = 192ggml_is_contiguous (x )&& ggml_is_contiguous (add )&& 193ggml_is_contiguous (a )&& ggml_is_contiguous (inv_b ); 194 195return types_ok && shape_ok && dim_ok && contig_ok && x_in_add == x ; 196} 197 198// ---- patterns ------------------------------------------------------------ 199 200static const ggml_op ops_norm_mul []= {GGML_OP_NORM ,GGML_OP_MUL }; 201static const ggml_op ops_norm_mul_add []= {GGML_OP_NORM ,GGML_OP_MUL ,GGML_OP_ADD }; 202static const ggml_op ops_rms_norm_mul []= {GGML_OP_RMS_NORM ,GGML_OP_MUL }; 203static const ggml_op ops_rms_norm_mul_add []= {GGML_OP_RMS_NORM ,GGML_OP_MUL ,GGML_OP_ADD }; 204 205static const ggml_op ops_add_2 []= {GGML_OP_ADD ,GGML_OP_ADD }; 206static const ggml_op ops_add_3 []= {GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD }; 207static const ggml_op ops_add_4 []= {GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD }; 208static const ggml_op ops_add_5 []= {GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD }; 209static const ggml_op ops_add_6 []= {GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD }; 210static const ggml_op ops_add_7 []= {GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD ,GGML_OP_ADD }; 211static const ggml_op ops_snake []= {GGML_OP_MUL ,GGML_OP_SIN ,GGML_OP_SQR ,GGML_OP_MUL ,GGML_OP_ADD }; 212 213static const ggml_op ops_gdn_cache []= {GGML_OP_GATED_DELTA_NET ,GGML_OP_CPY }; 214 215static const ggml_metal_fusion ggml_metal_fusions []= { 216 {GGML_METAL_FUSION_NORM_MUL ,ops_norm_mul ,2 , false,ggml_metal_fusion_check_norm }, 217 {GGML_METAL_FUSION_NORM_MUL_ADD ,ops_norm_mul_add ,3 , false,ggml_metal_fusion_check_norm }, 218 {GGML_METAL_FUSION_NORM_MUL ,ops_rms_norm_mul ,2 , false,ggml_metal_fusion_check_norm }, 219 {GGML_METAL_FUSION_NORM_MUL_ADD ,ops_rms_norm_mul_add ,3 , false,ggml_metal_fusion_check_norm }, 220 {GGML_METAL_FUSION_ADD_CHAIN ,ops_add_2 ,2 , false,ggml_metal_fusion_check_add_chain }, 221 {GGML_METAL_FUSION_ADD_CHAIN ,ops_add_3 ,3 , false,ggml_metal_fusion_check_add_chain }, 222 {GGML_METAL_FUSION_ADD_CHAIN ,ops_add_4 ,4 , false,ggml_metal_fusion_check_add_chain }, 223 {GGML_METAL_FUSION_ADD_CHAIN ,ops_add_5 ,5 , false,ggml_metal_fusion_check_add_chain }, 224 {GGML_METAL_FUSION_ADD_CHAIN ,ops_add_6 ,6 , false,ggml_metal_fusion_check_add_chain }, 225 {GGML_METAL_FUSION_ADD_CHAIN ,ops_add_7 ,7 , false,ggml_metal_fusion_check_add_chain }, 226 {GGML_METAL_FUSION_SNAKE ,ops_snake ,5 , false,ggml_metal_fusion_check_snake }, 227 {GGML_METAL_FUSION_GDN_CACHE ,ops_gdn_cache ,2 , true,ggml_metal_fusion_check_gdn_cache }, 228}; 229 230const ggml_metal_fusion * ggml_metal_fusion_all (int * n ) { 231* n = (int )sizeof (ggml_metal_fusions ) /sizeof (ggml_metal_fusions [0 ]); 232 233return ggml_metal_fusions ; 234} 235 236// ---- shared fusion info --------------------------------------------------- 237 238static std::string ggml_metal_fusion_label (const ggml_metal_fusion * fusion ) { 239GGML_ASSERT (fusion != nullptr ); 240 241 std::string label ; 242for (int j = 0 ;j < fusion -> n_ops ;j ++ ) { 243if (j > 0 ) { 244label += '+' ; 245 } 246label += ggml_op_name (fusion -> ops [j ]); 247 } 248return label ; 249} 250 251struct ggml_metal_fusion_info { 252 std::vector < std::string > labels ; 253 std::vector < uint64_t > counts ; 254bool enabled ; 255bool stats ; 256bool labels_set ; 257int debug ; 258}; 259 260struct ggml_metal_fusion_info * ggml_metal_fusion_info_init (bool enabled ,int debug ) { 261ggml_metal_fusion_info * finfo = new ggml_metal_fusion_info ; 262finfo -> enabled = enabled ; 263finfo -> stats = debug > 0 ; 264finfo -> labels_set = false; 265finfo -> debug = debug ; 266 267if (finfo -> stats ) { 268ggml_metal_fusion_info_labels_init (finfo ); 269 } 270 271return finfo ; 272} 273 274void ggml_metal_fusion_info_free (struct ggml_metal_fusion_info * finfo ) { 275delete finfo ; 276} 277 278bool ggml_metal_fusion_info_enabled (const struct ggml_metal_fusion_info * finfo ) { 279return finfo -> enabled ; 280} 281 282bool ggml_metal_fusion_info_stats (const struct ggml_metal_fusion_info * finfo ) { 283return finfo -> stats ; 284} 285 286int ggml_metal_fusion_info_debug (const struct ggml_metal_fusion_info * finfo ) { 287return finfo -> debug ; 288} 289 290int ggml_metal_fusion_info_n_fusions (const struct ggml_metal_fusion_info * finfo ) { 291return (int )finfo -> labels .size (); 292} 293 294const char * ggml_metal_fusion_info_label (const struct ggml_metal_fusion_info * finfo ,int idx ) { 295GGML_ASSERT (idx >=0 && idx < (int )finfo -> labels .size ()); 296return finfo -> labels [idx ].c_str (); 297} 298 299uint64_t ggml_metal_fusion_info_count (const struct ggml_metal_fusion_info * finfo ,int idx ) { 300GGML_ASSERT (idx >=0 && idx < (int )finfo -> counts .size ()); 301return finfo -> counts [idx ]; 302} 303 304void ggml_metal_fusion_info_count_fusion (struct ggml_metal_fusion_info * finfo ,const struct ggml_metal_fusion * fusion ) { 305if (!finfo -> stats || fusion == nullptr ) { 306return ; 307 } 308 309int n = 0 ; 310const ggml_metal_fusion * all = ggml_metal_fusion_all (& n ); 311 312int idx = -1 ; 313for (int i = 0 ;i < n ;i ++ ) { 314if (& all [i ]== fusion ) { 315idx = i ; 316break ; 317 } 318 } 319 320if (idx >=0 && idx < (int )finfo -> counts .size ()) { 321finfo -> counts [idx ]++ ; 322 } 323} 324 325void ggml_metal_fusion_info_set_enabled (struct ggml_metal_fusion_info * finfo ,bool enabled ) { 326finfo -> enabled = enabled ; 327} 328 329void ggml_metal_fusion_info_labels_init (struct ggml_metal_fusion_info * finfo ) { 330if (finfo -> labels_set ) { 331return ; 332 } 333 334int n = 0 ; 335const ggml_metal_fusion * all = ggml_metal_fusion_all (& n ); 336 337finfo -> labels .clear (); 338finfo -> counts .assign (n ,0 ); 339finfo -> labels .reserve (n ); 340 341for (int i = 0 ;i < n ;i ++ ) { 342finfo -> labels .emplace_back (ggml_metal_fusion_label (& all [i ])); 343 } 344 345finfo -> labels_set = true; 346} 347 348void ggml_metal_fusion_info_stats_init (struct ggml_metal_fusion_info * finfo ) { 349finfo -> stats = true; 350ggml_metal_fusion_info_labels_init (finfo ); 351} 352 353void ggml_metal_fusion_info_stats_reset (struct ggml_metal_fusion_info * finfo ) { 354 std::fill (finfo -> counts .begin (),finfo -> counts .end (),0 ); 355} 356 357int ggml_metal_fusion_info_stats_get (const struct ggml_metal_fusion_info * finfo ,const char ** labels ,uint64_t * counts ,int n ) { 358const int n_fusions = (int )finfo -> labels .size (); 359 360if (labels == nullptr ) { 361return n_fusions ; 362 } 363 364const int n_fill = std::min (n ,n_fusions ); 365for (int i = 0 ;i < n_fill ;i ++ ) { 366labels [i ]= finfo -> labels [i ].c_str (); 367if (counts != nullptr ) { 368counts [i ]= finfo -> counts [i ]; 369 } 370 } 371 372return n_fill ; 373} 374 375// ---- queries ------------------------------------------------------------- 376 377// find the longest pattern matching the node sequence starting at idx 378// (idx is a position in node_idxs, which maps to graph node indices) 379const ggml_metal_fusion * ggml_metal_fusion_next ( 380const ggml_cgraph * gf , 381const int * node_idxs , 382int n_idxs , 383int idx , 384ggml_metal_fusion_mode mode , 385int * n_out ) { 386int n = 0 ; 387const ggml_metal_fusion * all = ggml_metal_fusion_all (& n ); 388 389const ggml_metal_fusion * res = nullptr ; 390int best = 1 ; 391 392for (int i = 0 ;i < n ;i ++ ) { 393const ggml_metal_fusion * fusion = & all [i ]; 394 395// only look for a longer match than the current best 396if (fusion -> n_ops <=best ) { 397continue ; 398 } 399if (idx + fusion -> n_ops > n_idxs ) { 400continue ; 401 } 402 403const ggml_tensor * nodes [GGML_METAL_FUSION_MAX ]; 404 405// the op sequence must match exactly 406bool ok = true; 407for (int j = 0 ;j < fusion -> n_ops ;j ++ ) { 408nodes [j ]= gf -> nodes [node_idxs [idx + j ]]; 409if (nodes [j ]-> op != fusion -> ops [j ]) { 410ok = false; 411break ; 412 } 413 } 414if (!ok ) { 415continue ; 416 } 417 418if (!fusion -> unsafe ) { 419// common element-wise chain constraints: each node reads the previous one, 420// and all nodes have the same shape 421for (int j = 1 ;j < fusion -> n_ops && ok ;j ++ ) { 422if (nodes [j ]-> src [0 ]!= nodes [j - 1 ]&& nodes [j ]-> src [1 ]!= nodes [j - 1 ]) { 423ok = false; 424break ; 425 } 426if (!ggml_are_same_shape (nodes [j ],nodes [j - 1 ])) { 427ok = false; 428break ; 429 } 430 } 431if (!ok ) { 432continue ; 433 } 434 435// all current fusions are single-output elision chains, so the last node is the only output 436// TODO: multi-output fusions: store pattern-relative offsets in the table and translate them here 437int outputs_buf [1 ]; 438outputs_buf [0 ]= node_idxs [idx + fusion -> n_ops - 1 ]; 439 440// structural subgraph checks (op sequence, elidable uses, view containment) 441if (!ggml_can_fuse_subgraph_ext (gf ,node_idxs + idx ,fusion -> n_ops ,fusion -> ops ,outputs_buf ,1 )) { 442continue ; 443 } 444 } 445 446// pattern-specific checks (the sole validator for unsafe patterns) 447if (fusion -> check && !fusion -> check (fusion ,nodes ,mode )) { 448continue ; 449 } 450 451best = fusion -> n_ops ; 452res = fusion ; 453 } 454 455* n_out = best ; 456 457return res ; 458} 459 460// optimize phase: maximum number of nodes starting at idx (a raw sequential graph index) that 461// could be fused, chaining patterns back-to-back. matching runs on the same filtered (view 462// transparent) node sequence that the compute phase uses, so the returned count is the raw index 463// span from idx to the last matched node (intermediate views are packed along). 464int ggml_metal_fusion_max (const ggml_cgraph * gf ,int idx ) { 465// an empty/view node cannot start a pattern - pack it alone 466if (ggml_op_is_empty (gf -> nodes [idx ]-> op )|| ggml_is_empty (gf -> nodes [idx ])) { 467return 1 ; 468 } 469 470// collect the non-empty node indices starting at idx 471int idxs [GGML_METAL_FUSION_MAX ]; 472int n_idxs = 0 ; 473for (int i = idx ;i < gf -> n_nodes && n_idxs < GGML_METAL_FUSION_MAX ;i ++ ) { 474if (!ggml_op_is_empty (gf -> nodes [i ]-> op )&& !ggml_is_empty (gf -> nodes [i ])) { 475idxs [n_idxs ++ ]= i ; 476 } 477 } 478if (n_idxs == 0 ) { 479return 1 ; 480 } 481 482int total = 0 ; 483int i_f = 0 ; 484 485while (i_f < n_idxs && total < GGML_METAL_FUSION_MAX ) { 486int len = 1 ; 487const ggml_metal_fusion * fusion = ggml_metal_fusion_next (gf ,idxs ,n_idxs ,i_f ,GGML_METAL_FUSION_STRUCTURAL ,& len ); 488if (!fusion || total + len > GGML_METAL_FUSION_MAX ) { 489break ; 490 } 491 492total += len ; 493i_f += len ; 494 } 495 496if (i_f == 0 ) { 497return 1 ; 498 } 499 500// map the matched non-empty nodes back to the raw index span (views are included) 501return std::min (GGML_METAL_FUSION_MAX ,idxs [i_f - 1 ]- idx + 1 ); 502}