char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
efb3036c1
char/aliceai-t5-moe
1#include <cfloat> 2#include <initializer_list> 3#include <vector> 4 5#include "ggml.h" 6#include "ggml-impl.h" 7#include "ggml-backend-impl.h" 8#include "topk-moe.hpp" 9 10// SYCL port of ggml-cuda/topk-moe.cu. The kernel is a translation of the CUDA no-bias, no-PDL 11// path of topk_moe_cuda; the fusion-detection helpers below are ported near-verbatim from 12// ggml-cuda.cu (pure graph / pointer inspection, backend-agnostic). Bias is not implemented here: 13// if a routing bias is detected, the fusion is declined and the eager path runs unchanged. 14 15struct ggml_sycl_topk_moe_args { 16bool sigmoid {}; 17bool softmax {}; 18bool delayed_softmax {}; 19bool prob_bias {}; 20bool norm {}; 21bool scale {}; 22}; 23 24struct topk_moe_config { 25bool use_sigmoid ; 26bool with_norm ; 27bool delayed_softmax ; 28}; 29 30// warp-local softmax used for both the pre-top-k logits and the post-top-k delayed path 31template < int experts_per_thread ,bool use_limit > 32static inline void softmax_warp_inplace (float (& vals )[experts_per_thread ],const int limit ,const int lane ) { 33float max_val = - INFINITY ; 34#pragma unroll 35for (int i = 0 ;i < experts_per_thread ;i ++ ) { 36const int idx = lane + i * WARP_SIZE ; 37const bool active = !use_limit || (idx < limit ); 38if (active ) { 39max_val = sycl::fmax (max_val ,vals [i ]); 40 } 41 } 42max_val = warp_reduce_max < WARP_SIZE > (max_val ); 43 44float sum = 0.f ; 45#pragma unroll 46for (int i = 0 ;i < experts_per_thread ;i ++ ) { 47const int idx = lane + i * WARP_SIZE ; 48const bool active = !use_limit || (idx < limit ); 49if (active ) { 50const float val = sycl::exp (vals [i ]- max_val ); 51vals [i ]= val ; 52sum += val ; 53 }else { 54vals [i ]= 0.f ; 55 } 56 } 57sum = warp_reduce_sum < WARP_SIZE > (sum ); 58 59const float inv_sum = 1.0f /sum ; 60#pragma unroll 61for (int i = 0 ;i < experts_per_thread ;i ++ ) { 62const int idx = lane + i * WARP_SIZE ; 63if (!use_limit || idx < limit ) { 64vals [i ] *=inv_sum ; 65 } 66 } 67} 68 69template < int experts_per_thread ,bool use_limit > 70static inline void sigmoid_warp_inplace (float (& vals )[experts_per_thread ],const int limit ,const int lane ) { 71#pragma unroll 72for (int i = 0 ;i < experts_per_thread ;i ++ ) { 73const int idx = lane + i * WARP_SIZE ; 74const bool active = !use_limit || (idx < limit ); 75vals [i ]= active ?1.f / (1.f + sycl::exp (- vals [i ])) :- INFINITY ; 76 } 77} 78 79/* 80This kernel does the following: 811. optionally softmax/sigmoid over the logits per token [n_experts, n_tokens] 822. argmax reduce over the top-k (n_experts_used) logits 833. write weights + ids to global memory 844. optionally normalize the weights or apply softmax over the selected logits 85 86It is intended as a fusion of the softmax->top-k->get_rows pipeline for MoE models. 87One sub-group handles one row/token, mirroring topk_moe_cuda's one-warp-per-row layout. 88*/ 89template < int n_experts > 90static void topk_moe_kernel (const float * __restrict__logits , 91float * __restrict__weights , 92int32_t * __restrict__ids , 93const int n_rows , 94const int n_expert_used , 95const float clamp_val , 96const float scale_val , 97const topk_moe_config config ) { 98auto item_ct1 = sycl::ext::oneapi::this_work_item::get_nd_item < 1 > (); 99const int row = item_ct1 .get_group (0 ); 100if (row >=n_rows ) { 101return ; 102 } 103const int lane = item_ct1 .get_local_id (0 ); 104 105logits += (size_t )n_experts * row ; 106weights += (size_t )n_expert_used * row ; 107ids += (size_t )n_experts * row ;// ids row stride is n_experts (matches the argsort tensor) 108 109constexpr int experts_per_thread = (n_experts > WARP_SIZE ) ?n_experts /WARP_SIZE :1 ; 110 111float wt [experts_per_thread ]; 112#pragma unroll 113for (int i = 0 ;i < experts_per_thread ;i ++ ) { 114wt [i ]= - INFINITY ; 115 } 116#pragma unroll 117for (int i = 0 ;i < n_experts ;i += WARP_SIZE ) { 118const int expert = i + lane ; 119wt [i /WARP_SIZE ]= (n_experts %WARP_SIZE == 0 || expert < n_experts ) ?logits [expert ] :- INFINITY ; 120 } 121 122if (!config .delayed_softmax ) { 123if (config .use_sigmoid ) { 124sigmoid_warp_inplace < experts_per_thread , false> (wt ,n_experts ,lane ); 125 }else { 126softmax_warp_inplace < experts_per_thread , false> (wt ,n_experts ,lane ); 127 } 128 } 129 130// Sanitize NaN to -FLT_MAX so the iterative argmax produces unique expert IDs. NaN comparisons 131// always return false, which would cause the same expert to be selected repeatedly. 132#pragma unroll 133for (int i = 0 ;i < experts_per_thread ;i ++ ) { 134if (sycl::isnan (wt [i ])) { 135wt [i ]= - FLT_MAX ; 136 } 137 } 138 139// each thread now holds either a portion of the softmax distribution or the raw logits. Do the 140// argmax reduce over n_expert_used, each time marking the selected expert as -inf to exclude it 141// from the next iteration. 142 143float wt_sum = 0.f ; 144float output_weights [experts_per_thread ]; 145#pragma unroll 146for (int i = 0 ;i < experts_per_thread ;i ++ ) { 147output_weights [i ]= 0.f ; 148 } 149 150const sycl::sub_group sg = item_ct1 .get_sub_group (); 151 152for (int k = 0 ;k < n_expert_used ;k ++ ) { 153float max_val = wt [0 ]; 154int max_expert = lane ; 155#pragma unroll 156for (int i = 1 ;i < experts_per_thread ;i ++ ) { 157const int expert = lane + i * WARP_SIZE ; 158if ((n_experts %WARP_SIZE == 0 || expert < n_experts )&& wt [i ]> max_val ) { 159max_val = wt [i ]; 160max_expert = expert ; 161 } 162 } 163#pragma unroll 164for (int mask = WARP_SIZE /2 ;mask > 0 ;mask >>=1 ) { 165const float val = dpct::permute_sub_group_by_xor (sg ,max_val ,mask ); 166const int expert = dpct::permute_sub_group_by_xor (sg ,max_expert ,mask ); 167if (val > max_val || (val == max_val && expert < max_expert )) { 168max_val = val ; 169max_expert = expert ; 170 } 171 } 172 173if ((max_expert & (WARP_SIZE - 1 ))== lane ) { 174wt [max_expert /WARP_SIZE ]= - INFINITY ; 175 } 176if ((k & (WARP_SIZE - 1 ))== lane ) { 177output_weights [k /WARP_SIZE ]= max_val ; 178 } 179if ((max_expert & (WARP_SIZE - 1 ))== lane ) { 180ids [k ]= max_expert ; 181if (config .with_norm ) { 182wt_sum += max_val ; 183 } 184 } 185 } 186 187if (config .with_norm ) { 188wt_sum = warp_reduce_sum < WARP_SIZE > (wt_sum ); 189wt_sum = sycl::fmax (wt_sum ,clamp_val ); 190const float inv = 1.0f /wt_sum ; 191#pragma unroll 192for (int i = 0 ;i < experts_per_thread ;i ++ ) { 193output_weights [i ] *=inv ; 194 } 195 } 196 197if (config .delayed_softmax ) { 198softmax_warp_inplace < experts_per_thread , true> (output_weights ,n_expert_used ,lane ); 199 } 200 201#pragma unroll 202for (int i = 0 ;i < experts_per_thread ;i ++ ) { 203const int idx = i * WARP_SIZE + lane ; 204if (idx < n_expert_used ) { 205weights [idx ]= output_weights [i ]* scale_val ; 206 } 207 } 208} 209 210template < int n_experts > 211static void launch_topk_moe (queue_ptr stream ,const float * logits ,float * weights ,int32_t * ids ,int n_rows , 212int n_expert_used ,float clamp_val ,float scale_val ,const topk_moe_config & config ) { 213const sycl::range < 1 > block_dims (WARP_SIZE ); 214const sycl::range < 1 > block_nums (n_rows ); 215stream -> parallel_for (sycl::nd_range < 1 > (block_nums * block_dims ,block_dims ), 216 [= ](sycl::nd_item < 1 > item_ct1 ) [[sycl ::reqd_sub_group_size (WARP_SIZE )]] { 217topk_moe_kernel < n_experts > (logits ,weights ,ids ,n_rows ,n_expert_used ,clamp_val , 218scale_val ,config ); 219GGML_UNUSED (item_ct1 ); 220 }); 221} 222 223static void ggml_sycl_op_topk_moe (ggml_backend_sycl_context & ctx , 224const ggml_tensor * logits , 225ggml_tensor * weights , 226ggml_tensor * ids , 227const ggml_tensor * clamp , 228const ggml_tensor * scale , 229const ggml_sycl_topk_moe_args & args ) { 230GGML_ASSERT (logits -> type == GGML_TYPE_F32 ); 231GGML_ASSERT (weights -> type == GGML_TYPE_F32 ); 232GGML_ASSERT (ids -> type == GGML_TYPE_I32 ); 233 234const int n_experts = logits -> ne [0 ]; 235const int n_rows = logits -> ne [1 ]; 236const int n_expert_used = weights -> ne [1 ]; 237 238GGML_ASSERT (ids -> nb [1 ] /ggml_type_size (ids -> type )== (size_t )n_experts ); 239 240const float * logits_d = (const float * )logits -> data ; 241float * weights_d = (float * )weights -> data ; 242int32_t * ids_d = (int32_t * )ids -> data ; 243 244const bool with_norm = clamp != nullptr ; 245const float clamp_val = clamp ?ggml_get_op_params_f32 (clamp ,0 ) :- INFINITY ; 246const float scale_val = scale ?ggml_get_op_params_f32 (scale ,0 ) :1.0f ; 247 248topk_moe_config config ; 249config .use_sigmoid = args .sigmoid ; 250config .with_norm = with_norm ; 251config .delayed_softmax = args .delayed_softmax ; 252 253queue_ptr stream = ctx .stream (); 254ggml_sycl_set_device (ctx .device ); 255 256switch (n_experts ) { 257case 1 : 258launch_topk_moe < 1 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 259config ); 260break ; 261case 2 : 262launch_topk_moe < 2 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 263config ); 264break ; 265case 4 : 266launch_topk_moe < 4 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 267config ); 268break ; 269case 8 : 270launch_topk_moe < 8 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 271config ); 272break ; 273case 16 : 274launch_topk_moe < 16 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 275config ); 276break ; 277case 32 : 278launch_topk_moe < 32 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 279config ); 280break ; 281case 64 : 282launch_topk_moe < 64 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 283config ); 284break ; 285case 128 : 286launch_topk_moe < 128 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 287config ); 288break ; 289case 256 : 290launch_topk_moe < 256 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 291config ); 292break ; 293case 512 : 294launch_topk_moe < 512 > (stream ,logits_d ,weights_d ,ids_d ,n_rows ,n_expert_used ,clamp_val ,scale_val , 295config ); 296break ; 297default : 298GGML_ASSERT (false&& "fatal error" ); 299break ; 300 } 301} 302 303static bool ggml_sycl_should_use_topk_moe (const ggml_tensor * gating_op ,const ggml_tensor * weights , 304const ggml_tensor * logits ,const ggml_tensor * ids ) { 305const int n_expert = ids -> nb [1 ] /ids -> nb [0 ]; 306if ((n_expert & (n_expert - 1 ))!= 0 || n_expert > 512 ) { 307return false; 308 } 309 310if (!ggml_is_contiguous (weights )|| !ggml_is_contiguous (logits )) { 311return false; 312 } 313 314if (gating_op -> op == GGML_OP_SOFT_MAX ) { 315float scale = 1.0f ; 316float max_bias = 0.0f ; 317 318memcpy (& scale , (const float * )gating_op -> op_params + 0 ,sizeof (float )); 319memcpy (& max_bias , (const float * )gating_op -> op_params + 1 ,sizeof (float )); 320 321if (!ggml_is_contiguous (gating_op -> src [0 ])) { 322return false; 323 } 324if (scale != 1.0f || max_bias != 0.0f ) { 325return false; 326 } 327// don't fuse when masks or sinks are present 328if (gating_op -> src [1 ]|| gating_op -> src [2 ]) { 329return false; 330 } 331 }else if (gating_op -> op == GGML_OP_UNARY ) { 332if (ggml_get_unary_op (gating_op )!= GGML_UNARY_OP_SIGMOID ) { 333return false; 334 } 335 } 336 337return true; 338} 339 340// ported from ggml_cuda_topk_moe_fusion - pure graph inspection, backend-agnostic 341static bool ggml_sycl_topk_moe_fusion (const ggml_cgraph * cgraph ,int node_idx ,ggml_sycl_topk_moe_args & args ) { 342args = ggml_sycl_topk_moe_args {}; 343 344const int n_nodes = cgraph -> n_nodes ; 345ggml_tensor ** nodes = cgraph -> nodes ; 346 347if (nodes [node_idx ]-> op == GGML_OP_SOFT_MAX ) { 348args .softmax = true; 349 } 350 351if (nodes [node_idx ]-> op == GGML_OP_UNARY ) { 352if (ggml_get_unary_op (nodes [node_idx ])!= GGML_UNARY_OP_SIGMOID ) { 353return false; 354 } 355args .sigmoid = true; 356 } 357 358if (nodes [node_idx ]-> op == GGML_OP_ARGSORT ) { 359args .delayed_softmax = true; 360 } 361 362node_idx ++ ; 363 364if (args .sigmoid || args .softmax ) { 365// SOFTMAX -> RESHAPE 366if (node_idx >=n_nodes || nodes [node_idx ]-> op != GGML_OP_RESHAPE || 367nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 1 ]) { 368return false; 369 } 370ggml_tensor * probs_reshaped = nodes [node_idx ]; 371node_idx ++ ; 372 373if (node_idx >=n_nodes ) { 374return false; 375 } 376 377// src of bias add is the unreshaped probs (-2 instead of -1) 378if (nodes [node_idx ]-> op == GGML_OP_ADD && nodes [node_idx ]-> src [0 ]== nodes [node_idx - 2 ]) { 379args .prob_bias = true; 380node_idx ++ ; 381 } 382// RESHAPE/ADD -> ARGSORT 383if (node_idx >=n_nodes || nodes [node_idx ]-> op != GGML_OP_ARGSORT ) { 384return false; 385 } 386 387if (args .prob_bias && nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 1 ]) { 388return false; 389 }else if (!args .prob_bias && nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 2 ]) { 390return false; 391 } 392 393node_idx ++ ; 394 395// ARGSORT -> VIEW 396if (node_idx >=n_nodes || nodes [node_idx ]-> op != GGML_OP_VIEW || 397nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 1 ]) { 398return false; 399 } 400node_idx ++ ; 401 402if (node_idx >=n_nodes || nodes [node_idx ]-> op != GGML_OP_GET_ROWS ) { 403return false; 404 } 405 406// GET_ROWS 407if (nodes [node_idx ]-> src [0 ]!= probs_reshaped || nodes [node_idx ]-> src [1 ]!= nodes [node_idx - 1 ]) { 408return false; 409 } 410node_idx ++ ; 411 }else if (args .delayed_softmax ) { 412if (node_idx - 2 < 0 ) { 413return false; 414 } 415ggml_tensor * probs_reshaped = nodes [node_idx - 2 ]; 416 417// VIEW -> ARGSORT 418if (node_idx >=n_nodes || nodes [node_idx ]-> op != GGML_OP_VIEW || 419nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 1 ]) { 420return false; 421 } 422node_idx ++ ; 423 424// GET_ROWS 425if (node_idx >=n_nodes || nodes [node_idx ]-> src [1 ]!= nodes [node_idx - 1 ]|| 426nodes [node_idx ]-> src [0 ]!= probs_reshaped ) { 427return false; 428 } 429node_idx ++ ; 430 431static const std::vector < ggml_op > remaining_ops = {GGML_OP_RESHAPE ,GGML_OP_SOFT_MAX ,GGML_OP_RESHAPE }; 432 433for (const ggml_op op :remaining_ops ) { 434if (node_idx >=n_nodes || nodes [node_idx ]-> op != op || nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 1 ]) { 435return false; 436 } 437node_idx ++ ; 438 } 439 } 440 441// at this point we can check for norm + scale; everything is now at least valid up to the norm 442if (node_idx >=n_nodes ) { 443return true; 444 } 445 446if (nodes [node_idx ]-> op == GGML_OP_RESHAPE ) { 447// check RESHAPE -> SUM_ROWS -> CLAMP -> DIV -> RESHAPE 448static const std::vector < ggml_op > norm_ops = {GGML_OP_RESHAPE ,GGML_OP_SUM_ROWS ,GGML_OP_CLAMP }; 449 450args .norm = true; 451for (const ggml_op op :norm_ops ) { 452if (nodes [node_idx ]-> op == op && nodes [node_idx ]-> src [0 ]== nodes [node_idx - 1 ]) { 453node_idx ++ ; 454 }else { 455args .norm = false; 456return true; 457 } 458 } 459 460// DIV <- CLAMP, RESHAPE 461if (nodes [node_idx ]-> op != GGML_OP_DIV || nodes [node_idx ]-> src [1 ]!= nodes [node_idx - 1 ]|| 462nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 3 ]) { 463args .norm = false; 464return true; 465 } 466node_idx ++ ; 467 468if (nodes [node_idx ]-> op != GGML_OP_RESHAPE || nodes [node_idx ]-> src [0 ]!= nodes [node_idx - 1 ]) { 469args .norm = false; 470return true; 471 } 472node_idx ++ ; 473 } 474 475if (nodes [node_idx ]-> op == GGML_OP_SCALE && nodes [node_idx ]-> src [0 ]== nodes [node_idx - 1 ]) { 476args .scale = true; 477 } 478 479return true; 480} 481 482// returns whether the write (out) nodes overwrite the read nodes in operation 483// ported from ggml_cuda_check_fusion_memory_ranges - pure pointer/range inspection 484static bool ggml_sycl_check_fusion_memory_ranges (const ggml_cgraph * cgraph ,const int node_idx , 485const int node_count ,const int * out_nodes ,const int out_count , 486const bool is_topk_moe = false) { 487auto nodes_overlap = [& ](const ggml_tensor * a ,const ggml_tensor * b ) { 488const int64_t a_start = (int64_t )a -> data ; 489const int64_t a_end = a_start + ggml_backend_buft_get_alloc_size (a -> buffer -> buft ,a ); 490 491const int64_t b_start = (int64_t )b -> data ; 492const int64_t b_end = b_start + ggml_backend_buft_get_alloc_size (b -> buffer -> buft ,b ); 493 494if ((b_start <=a_start && a_start < b_end )|| (a_start <=b_start && b_start < a_end )) { 495return true; 496 } 497 498return false; 499 }; 500 501bool is_ok = true; 502// exception for topk-moe, as each row is read entirely before writing 503if (ggml_nrows (cgraph -> nodes [node_idx ])== 1 && is_topk_moe ) { 504return true; 505 } 506 507for (int i = 0 ;i < out_count ;++ i ) { 508const ggml_tensor * dst = cgraph -> nodes [out_nodes [i ]]; 509 510for (int j = node_idx ;j < node_idx + node_count ;++ j ) { 511// loop over all srcs of all nodes in the fusion. If the src overlaps the destination and 512// the src is not an intermediate node that's being elided, then disable fusion. 513for (int src_idx = 0 ;src_idx < GGML_MAX_SRC ;++ src_idx ) { 514const ggml_tensor * src = cgraph -> nodes [j ]-> src [src_idx ]; 515 516if (!src || src -> op == GGML_OP_NONE ) { 517continue ; 518 } 519 520if (nodes_overlap (dst ,src )) { 521bool found = false; 522 523for (int k = node_idx ;k < j ;++ k ) { 524if (cgraph -> nodes [k ]== src ) { 525found = true; 526break ; 527 } 528 } 529 530if (!found ) { 531is_ok = false; 532break ; 533 } 534 } 535 } 536 } 537 } 538 539return is_ok ; 540} 541 542int ggml_sycl_fuse (ggml_backend_sycl_context & ctx ,ggml_cgraph * cgraph ,int i ) { 543if (!g_ggml_sycl_enable_fusion ) { 544return 0 ; 545 } 546 547return ggml_sycl_fuse_topk_moe (ctx ,cgraph ,i ); 548} 549 550int ggml_sycl_fuse_topk_moe (ggml_backend_sycl_context & ctx ,ggml_cgraph * cgraph ,int i ) { 551ggml_tensor * node = cgraph -> nodes [i ]; 552 553if (node -> op != GGML_OP_UNARY && node -> op != GGML_OP_SOFT_MAX && node -> op != GGML_OP_ARGSORT ) { 554return 0 ; 555 } 556 557ggml_sycl_topk_moe_args args ; 558if (!ggml_sycl_topk_moe_fusion (cgraph ,i ,args )) { 559return 0 ; 560 } 561 562// this kernel implements the no-bias path only; decline anything with a routing bias 563if (args .prob_bias ) { 564return 0 ; 565 } 566 567const ggml_tensor * logits = node -> src [0 ]; 568ggml_tensor * weights = nullptr ; 569ggml_tensor * ids = nullptr ; 570const ggml_tensor * clamp = nullptr ; 571const ggml_tensor * scale = nullptr ; 572 573 std::vector < ggml_op > ops ; 574int out_nodes [2 ]; 575 576if (!args .delayed_softmax ) { 577const ggml_op gating_op = args .sigmoid ?GGML_OP_UNARY :GGML_OP_SOFT_MAX ; 578ops .insert (ops .end (), {gating_op ,GGML_OP_RESHAPE ,GGML_OP_ARGSORT ,GGML_OP_VIEW ,GGML_OP_GET_ROWS }); 579out_nodes [0 ]= i + 3 ; 580ids = cgraph -> nodes [i + 3 ]; 581 582if (args .norm ) { 583ops .insert (ops .end (), {GGML_OP_RESHAPE ,GGML_OP_SUM_ROWS ,GGML_OP_CLAMP ,GGML_OP_DIV ,GGML_OP_RESHAPE }); 584clamp = cgraph -> nodes [i + (int )ops .size ()- 3 ]; 585 } 586if (args .scale ) { 587ops .insert (ops .end (), {GGML_OP_SCALE }); 588scale = cgraph -> nodes [i + (int )ops .size ()- 1 ]; 589 } 590 591weights = cgraph -> nodes [i + (int )ops .size ()- 1 ]; 592out_nodes [1 ]= i + (int )ops .size ()- 1 ; 593 594if (ggml_can_fuse_subgraph (cgraph ,i ,ops .size (),ops .data (),out_nodes ,2 )&& 595ggml_sycl_should_use_topk_moe (node ,weights ,logits ,ids )&& 596ggml_sycl_check_fusion_memory_ranges (cgraph ,i , (int )ops .size (),out_nodes ,2 ,/*is_topk_moe=*/ true)) { 597ggml_sycl_op_topk_moe (ctx ,logits ,weights ,ids ,clamp ,scale ,args ); 598return (int )ops .size ()- 1 ; 599 } 600 }else if (!args .norm && !args .prob_bias ) { 601// gpt-oss style: argsort -> view -> get_rows -> reshape -> softmax -> reshape, no norm/bias 602ops .insert (ops .end (), 603 {GGML_OP_ARGSORT ,GGML_OP_VIEW ,GGML_OP_GET_ROWS ,GGML_OP_RESHAPE ,GGML_OP_SOFT_MAX , 604GGML_OP_RESHAPE }); 605weights = cgraph -> nodes [i + 5 ]; 606ids = cgraph -> nodes [i + 1 ]; 607const ggml_tensor * softmax = cgraph -> nodes [i + 4 ]; 608out_nodes [0 ]= i + 1 ; 609out_nodes [1 ]= i + 5 ; 610 611if (ggml_can_fuse_subgraph (cgraph ,i ,ops .size (),ops .data (),out_nodes ,2 )&& 612ggml_sycl_should_use_topk_moe (softmax ,weights ,logits ,ids )&& 613ggml_sycl_check_fusion_memory_ranges (cgraph ,i , (int )ops .size (),out_nodes ,2 ,/*is_topk_moe=*/ true)) { 614ggml_sycl_op_topk_moe (ctx ,logits ,weights ,ids ,clamp ,scale ,args ); 615return (int )ops .size ()- 1 ; 616 } 617 } 618 619return 0 ; 620}