char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
a2878d30d
char/aliceai-t5-moe
1#include "ggml-metal-common.h" 2#include "ggml-metal-fusion.h" 3 4#include "ggml.h" 5#include "ggml-impl.h" 6#include "ggml-backend-impl.h" 7 8#include <vector> 9 10bool ggml_metal_op_mul_mat_use_mm (const struct ggml_tensor * op ,bool has_simdgroup_mm ) { 11const int64_t ne00 = op -> src [0 ]-> ne [0 ]; 12const int64_t ne11 = op -> src [1 ]-> ne [1 ]; 13 14return !ggml_is_transposed (op -> src [0 ])&& 15 !ggml_is_transposed (op -> src [1 ])&& 16has_simdgroup_mm && ne00 >=64 && ne11 > 8 ; 17} 18 19bool ggml_metal_op_mul_mat_id_use_mm (const struct ggml_tensor * op ,bool has_simdgroup_mm ) { 20const int64_t ne00 = op -> src [0 ]-> ne [0 ]; 21const int64_t ne21 = op -> src [2 ]-> ne [1 ]; 22 23return has_simdgroup_mm && ne00 >=64 && ne21 >=32 ; 24} 25 26// represents a memory range (i.e. an interval from a starting address p0 to an ending address p1 in a given buffer pb) 27// the type indicates whether it is a source range (i.e. ops read data from it) or a destination range (i.e. ops write data to it) 28struct ggml_mem_range { 29uint64_t pb ;// buffer id 30 31uint64_t p0 ;// begin 32uint64_t p1 ;// end 33 34ggml_mem_range_type pt ; 35}; 36 37struct ggml_mem_ranges { 38 std::vector < ggml_mem_range > ranges ; 39 40int debug = 0 ; 41}; 42 43ggml_mem_ranges_t ggml_mem_ranges_init (int debug ) { 44auto * res = new ggml_mem_ranges ; 45 46res -> ranges .reserve (256 ); 47res -> debug = debug ; 48 49return res ; 50} 51 52void ggml_mem_ranges_free (ggml_mem_ranges_t mrs ) { 53delete mrs ; 54} 55 56void ggml_mem_ranges_reset (ggml_mem_ranges_t mrs ) { 57mrs -> ranges .clear (); 58} 59 60static bool ggml_mem_ranges_add (ggml_mem_ranges_t mrs ,ggml_mem_range mr ) { 61mrs -> ranges .push_back (mr ); 62 63return true; 64} 65 66static ggml_mem_range ggml_mem_range_from_tensor (const ggml_tensor * tensor ,ggml_mem_range_type pt ) { 67// always use the base tensor 68tensor = tensor -> view_src ?tensor -> view_src :tensor ; 69 70GGML_ASSERT (!tensor -> view_src ); 71 72ggml_mem_range mr ; 73 74if (tensor -> buffer ) { 75// when the tensor is allocated, use the actual memory address range in the buffer 76// 77// take the actual allocated size with ggml_backend_buft_get_alloc_size() 78// this can be larger than the tensor size if the buffer type allocates extra memory 79// ref: https://github.com/ggml-org/llama.cpp/pull/15966 80mr = { 81/*.pb =*/ (uint64_t )tensor -> buffer , 82/*.p0 =*/ (uint64_t )tensor -> data , 83/*.p1 =*/ (uint64_t )tensor -> data + ggml_backend_buft_get_alloc_size (tensor -> buffer -> buft ,tensor ), 84/*.pt =*/ pt , 85 }; 86 }else { 87// otherwise, the pointer address is used as an unique id of the memory ranges 88// that the tensor will be using when it is allocated 89mr = { 90/*.pb =*/ (uint64_t )tensor , 91/*.p0 =*/ 0 ,// 92/*.p1 =*/ 1024 ,// [0, 1024) is a dummy range, not used 93/*.pt =*/ pt , 94 }; 95 }; 96 97return mr ; 98} 99 100static ggml_mem_range ggml_mem_range_from_tensor_src (const ggml_tensor * tensor ) { 101return ggml_mem_range_from_tensor (tensor ,MEM_RANGE_TYPE_SRC ); 102} 103 104static ggml_mem_range ggml_mem_range_from_tensor_dst (const ggml_tensor * tensor ) { 105return ggml_mem_range_from_tensor (tensor ,MEM_RANGE_TYPE_DST ); 106} 107 108static bool ggml_mem_ranges_add_src (ggml_mem_ranges_t mrs ,const ggml_tensor * tensor ) { 109GGML_ASSERT (tensor ); 110 111ggml_mem_range mr = ggml_mem_range_from_tensor_src (tensor ); 112 113if (mrs -> debug > 2 ) { 114GGML_LOG_DEBUG ("%s: add src range buf=%lld, [%lld, %lld)\n" ,__func__ ,mr .pb ,mr .p0 ,mr .p1 ); 115 } 116 117return ggml_mem_ranges_add (mrs ,mr ); 118} 119 120static bool ggml_mem_ranges_add_dst (ggml_mem_ranges_t mrs ,const ggml_tensor * tensor ) { 121GGML_ASSERT (tensor ); 122 123ggml_mem_range mr = ggml_mem_range_from_tensor_dst (tensor ); 124 125if (mrs -> debug > 2 ) { 126GGML_LOG_DEBUG ("%s: add dst range buf=%lld, [%lld, %lld)\n" ,__func__ ,mr .pb ,mr .p0 ,mr .p1 ); 127 } 128 129return ggml_mem_ranges_add (mrs ,mr ); 130} 131 132bool ggml_mem_ranges_add (ggml_mem_ranges_t mrs ,const ggml_tensor * tensor ) { 133for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 134if (tensor -> src [i ]) { 135ggml_mem_ranges_add_src (mrs ,tensor -> src [i ]); 136 } 137 } 138 139return ggml_mem_ranges_add_dst (mrs ,tensor ); 140} 141 142static bool ggml_mem_ranges_check (ggml_mem_ranges_t mrs ,ggml_mem_range mr ) { 143for (size_t i = 0 ;i < mrs -> ranges .size ();i ++ ) { 144const auto & cmp = mrs -> ranges [i ]; 145 146// two memory ranges cannot intersect if they are in different buffers 147if (mr .pb != cmp .pb ) { 148continue ; 149 } 150 151// intersecting source ranges are allowed 152if (mr .pt == MEM_RANGE_TYPE_SRC && cmp .pt == MEM_RANGE_TYPE_SRC ) { 153continue ; 154 } 155 156if (mr .p0 < cmp .p1 && mr .p1 >=cmp .p0 ) { 157if (mrs -> debug > 2 ) { 158GGML_LOG_DEBUG ("%s: the %s range buf=%lld, [%lld, %lld) overlaps with a previous %s range buf=%lld, [%lld, %lld)\n" , 159__func__ , 160mr .pt == MEM_RANGE_TYPE_SRC ?"src" :"dst" , 161mr .pb ,mr .p0 ,mr .p1 , 162cmp .pt == MEM_RANGE_TYPE_SRC ?"src" :"dst" , 163cmp .pb ,cmp .p0 ,cmp .p1 ); 164 } 165 166return false; 167 } 168 } 169 170return true; 171} 172 173static bool ggml_mem_ranges_check_src (ggml_mem_ranges_t mrs ,const ggml_tensor * tensor ) { 174GGML_ASSERT (tensor ); 175 176ggml_mem_range mr = ggml_mem_range_from_tensor_src (tensor ); 177 178const bool res = ggml_mem_ranges_check (mrs ,mr ); 179 180return res ; 181} 182 183static bool ggml_mem_ranges_check_dst (ggml_mem_ranges_t mrs ,const ggml_tensor * tensor ) { 184GGML_ASSERT (tensor ); 185 186ggml_mem_range mr = ggml_mem_range_from_tensor_dst (tensor ); 187 188const bool res = ggml_mem_ranges_check (mrs ,mr ); 189 190return res ; 191} 192 193bool ggml_mem_ranges_check (ggml_mem_ranges_t mrs ,const ggml_tensor * tensor ) { 194for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 195if (tensor -> src [i ]) { 196if (!ggml_mem_ranges_check_src (mrs ,tensor -> src [i ])) { 197return false; 198 } 199 } 200 } 201 202return ggml_mem_ranges_check_dst (mrs ,tensor ); 203} 204 205struct node_info { 206ggml_tensor * node ; 207 208 std::vector < ggml_tensor *> fused ; 209 210ggml_op op ()const { 211return node -> op ; 212 } 213 214const ggml_tensor * dst ()const { 215return fused .empty () ?node :fused .back (); 216 } 217 218bool is_empty ()const { 219return ggml_op_is_empty (node -> op ); 220 } 221 222void add_fused (ggml_tensor * t ) { 223fused .push_back (t ); 224 } 225}; 226 227static std::vector < int > ggml_metal_graph_optimize_reorder (const std::vector < node_info > & nodes ) { 228// helper to add node src and dst ranges 229const auto & h_add = [](ggml_mem_ranges_t mrs ,const node_info & node ) { 230for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 231if (node .node -> src [i ]) { 232if (!ggml_mem_ranges_add_src (mrs ,node .node -> src [i ])) { 233return false; 234 } 235 } 236 } 237 238// keep track of the sources of the fused nodes as well 239for (const auto * fused :node .fused ) { 240for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 241if (fused -> src [i ]) { 242if (!ggml_mem_ranges_add_src (mrs ,fused -> src [i ])) { 243return false; 244 } 245 } 246 } 247 } 248 249return ggml_mem_ranges_add_dst (mrs ,node .dst ()); 250 }; 251 252// helper to check if a node can run concurrently with the existing set of nodes 253const auto & h_check = [](ggml_mem_ranges_t mrs ,const node_info & node ) { 254for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 255if (node .node -> src [i ]) { 256if (!ggml_mem_ranges_check_src (mrs ,node .node -> src [i ])) { 257return false; 258 } 259 } 260 } 261 262for (const auto * fused :node .fused ) { 263for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 264if (fused -> src [i ]) { 265if (!ggml_mem_ranges_check_src (mrs ,fused -> src [i ])) { 266return false; 267 } 268 } 269 } 270 } 271 272return ggml_mem_ranges_check_dst (mrs ,node .dst ()); 273 }; 274 275// perform reorders only across these types of ops 276// can be expanded when needed 277const auto & h_safe = [](ggml_op op ) { 278switch (op ) { 279case GGML_OP_MUL_MAT : 280case GGML_OP_MUL_MAT_ID : 281case GGML_OP_ROPE : 282case GGML_OP_NORM : 283case GGML_OP_RMS_NORM : 284case GGML_OP_GROUP_NORM : 285case GGML_OP_L2_NORM : 286case GGML_OP_SUM_ROWS : 287case GGML_OP_SSM_CONV : 288case GGML_OP_SSM_SCAN : 289case GGML_OP_CLAMP : 290case GGML_OP_TRI : 291case GGML_OP_DIAG : 292case GGML_OP_MUL : 293case GGML_OP_ADD : 294case GGML_OP_SUB : 295case GGML_OP_DIV : 296case GGML_OP_GLU : 297case GGML_OP_SCALE : 298case GGML_OP_UNARY : 299case GGML_OP_GET_ROWS : 300case GGML_OP_SET_ROWS : 301case GGML_OP_SET : 302case GGML_OP_CPY : 303case GGML_OP_CONT : 304case GGML_OP_REPEAT : 305return true; 306default : 307return ggml_op_is_empty (op ); 308 } 309 }; 310 311const int n = nodes .size (); 312 313 std::vector < int > res ; 314res .reserve (n ); 315 316 std::vector < bool > used (n ,false ); 317 318// the memory ranges for the set of currently concurrent nodes 319ggml_mem_ranges_t mrs0 = ggml_mem_ranges_init (0 ); 320 321// the memory ranges for the set of nodes that haven't been processed yet, when looking forward for a node to reorder 322ggml_mem_ranges_t mrs1 = ggml_mem_ranges_init (0 ); 323 324for (int i0 = 0 ;i0 < n ;i0 ++ ) { 325if (used [i0 ]) { 326continue ; 327 } 328 329const auto & node0 = nodes [i0 ]; 330 331// the node is not concurrent with the existing concurrent set, so we have to "put a barrier" (i.e reset mrs0) 332// but before we do that, look forward for some other nodes that can be added to the concurrent set mrs0 333// 334// note: we can always add empty nodes to the concurrent set as they don't read nor write anything 335if (!node0 .is_empty ()&& !h_check (mrs0 ,node0 )) { 336// this will hold the set of memory ranges from the nodes that haven't been processed yet 337// if a node is not concurrent with this set, we cannot reorder it 338ggml_mem_ranges_reset (mrs1 ); 339 340// initialize it with the current node 341h_add (mrs1 ,node0 ); 342 343// that many nodes forward to search for a concurrent node 344constexpr int N_FORWARD = 64 ; 345 346for (int i1 = i0 + 1 ;i1 < i0 + N_FORWARD && i1 < n ;i1 ++ ) { 347if (used [i1 ]) { 348continue ; 349 } 350 351const auto & node1 = nodes [i1 ]; 352 353// disallow reordering of certain ops 354if (!h_safe (node1 .op ())) { 355break ; 356 } 357 358const bool is_empty = node1 .is_empty (); 359 360// to reorder a node and add it to the concurrent set, it has to be: 361// + empty or concurrent with all nodes in the existing concurrent set (mrs0) 362// + concurrent with all nodes prior to it that haven't been processed yet (mrs1) 363if ((is_empty || h_check (mrs0 ,node1 ))&& h_check (mrs1 ,node1 )) { 364// add the node to the existing concurrent set (i.e. reorder it for early execution) 365h_add (mrs0 ,node1 ); 366res .push_back (i1 ); 367 368// mark as used, so we skip re-processing it later 369used [i1 ]= true; 370 }else { 371// expand the set of nodes that haven't been processed yet 372h_add (mrs1 ,node1 ); 373 } 374 } 375 376// finalize the concurrent set and begin a new one 377ggml_mem_ranges_reset (mrs0 ); 378 } 379 380// expand the concurrent set with the current node 381 { 382h_add (mrs0 ,node0 ); 383res .push_back (i0 ); 384 } 385 } 386 387ggml_mem_ranges_free (mrs0 ); 388ggml_mem_ranges_free (mrs1 ); 389 390return res ; 391} 392 393void ggml_graph_optimize (ggml_cgraph * gf ) { 394const int n = gf -> n_nodes ; 395 396 std::vector < node_info > nodes ; 397nodes .reserve (gf -> n_nodes ); 398 399// fuse nodes: 400// we don't want to make reorders that break fusing, so we first pack all fusable tensors 401// and perform the reorder over the fused nodes. after the reorder is done, we unfuse 402// 403// the fusable sequences are declared in the fusion table (ggml-metal-fuse.cpp), so the 404// packing here is driven by the same patterns that the op encoders will later use 405for (int i = 0 ;i < n ;i ++ ) { 406node_info node = { 407/*.node =*/ gf -> nodes [i ], 408/*.fused =*/ {}, 409 }; 410 411const int f = ggml_metal_fusion_max (gf ,i ); 412 413// add the fused tensors into the node info so we can unfuse them later 414for (int k = 1 ;k < f ;k ++ ) { 415++ i ; 416 417// the .dst() becomes the last fused tensor 418node .add_fused (gf -> nodes [i ]); 419 } 420 421nodes .push_back (std::move (node )); 422 } 423 424#if 1 425// reorder to improve concurrency 426const auto order = ggml_metal_graph_optimize_reorder (nodes ); 427#else 428 std::vector < int > order (nodes .size ()); 429for (size_t i = 0 ;i < nodes .size ();i ++ ) { 430order [i ]= i ; 431 } 432#endif 433 434// unfuse 435 { 436int j = 0 ; 437for (const auto i :order ) { 438const auto & node = nodes [i ]; 439 440gf -> nodes [j ++ ]= node .node ; 441 442for (auto * fused :node .fused ) { 443gf -> nodes [j ++ ]= fused ; 444 } 445 } 446 } 447}