char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
b81c99b47
char/aliceai-t5-moe
1// SPDX-FileCopyrightText: Copyright 2025-2026 Arm Limited and/or its affiliates <open-source-office@arm.com> 2// SPDX-License-Identifier: MIT 3// 4#include <arm_neon.h> 5#include <cassert> 6#include <cstdio> 7#include <cstdlib> 8#include <atomic> 9#include <cfloat> 10#include <cctype> 11#include <algorithm> 12#include <cmath> 13#include <stdexcept> 14#include <stdint.h> 15#include <string.h> 16#include <string> 17#include <vector> 18#include <array> 19#include <cstddef> 20#include <cstdint> 21#include <fstream> 22#include <map> 23#include <iostream> 24#include <climits> 25#include <charconv> 26#include <system_error> 27#if defined(__linux__ ) 28#include <asm/hwcap.h> 29#include <dirent.h> 30#include <sys/auxv.h> 31#include <sys/types.h> 32#include <sys/stat.h> 33#include <unistd.h> 34#elif defined(__APPLE__ ) 35#include <sys/sysctl.h> 36#include <sys/types.h> 37#endif 38 39#include "kleidiai.h" 40 41#include "ggml-cpu.h" 42#include "ggml-cpu-impl.h" 43#include "ggml-impl.h" 44#include "ggml-feats.h" 45#include "ggml-backend-impl.h" 46#include "ggml-threading.h" 47#include "traits.h" 48 49#include "kernels.h" 50 51#include "kai/kai_common.h" 52 53#define GGML_COMMON_DECL_CPP 54#include "ggml-common.h" 55 56static constexpr int GGML_KLEIDIAI_MAX_KERNEL_SLOTS = 2 ; 57static constexpr uint32_t GGML_KLEIDIAI_PACK_MAGIC = 0x4b4c4149 ;// "KLAI" 58static constexpr uint16_t GGML_KLEIDIAI_PACK_VERSION = 1 ; 59static constexpr size_t GGML_KLEIDIAI_PACK_ALIGN = 64 ; 60 61struct ggml_kleidiai_context { 62cpu_feature features ; 63ggml_kleidiai_kernels * kernels_q4 ; 64ggml_kleidiai_kernels * kernels_q8 ; 65ggml_kleidiai_kernels * kernels_f32 ; 66int sme_thread_cap ;// <= 0 means "SME disabled/unknown" 67int thread_hint ;// <= 0 means "no hint" 68int chunk_multiplier ; 69}static ctx = {CPU_FEATURE_NONE ,nullptr ,nullptr ,nullptr ,0 ,-1 ,4 }; 70 71static inline bool is_sme_family (cpu_feature f ) { 72return (f & (CPU_FEATURE_SME |CPU_FEATURE_SME2 ))!= CPU_FEATURE_NONE ; 73} 74 75static const char * cpu_feature_to_string (cpu_feature f ) { 76if (f == CPU_FEATURE_NONE ) { 77return "NONE" ; 78 }else if ((f & CPU_FEATURE_SME2 )== CPU_FEATURE_SME2 ) { 79return "SME2" ; 80 }else if ((f & CPU_FEATURE_SME )== CPU_FEATURE_SME ) { 81return "SME" ; 82 }else if ((f & CPU_FEATURE_SVE )== CPU_FEATURE_SVE ) { 83return "SVE" ; 84 } 85else if ((f & CPU_FEATURE_I8MM )== CPU_FEATURE_I8MM ) { 86return "I8MM" ; 87 }else if ((f & CPU_FEATURE_DOTPROD )== CPU_FEATURE_DOTPROD ) { 88return "DOTPROD" ; 89 } 90else { 91return "UNKNOWN" ; 92 } 93} 94 95#if defined(__linux__ )&& defined(__aarch64__ ) 96static bool parse_cpu_dir_name (const char * name ,size_t * cpu ) { 97if (strncmp (name ,"cpu" ,3 )!= 0 || 98name [3 ]< '0' || name [3 ]> '9' ) { 99return false; 100 } 101 102const char * first = name + 3 ; 103const char * last = name + strlen (name ); 104 105size_t value = 0 ; 106const auto [end ,ec ]= std::from_chars (first ,last ,value ,10 ); 107 108if (ec != std::errc {}|| end != last ) { 109return false; 110 } 111 112* cpu = value ; 113return true; 114} 115 116static std::vector < size_t > detect_cpu_ids () { 117 std::vector < size_t > cpus ; 118 119DIR * dir = opendir ("/sys/devices/system/cpu" ); 120if (dir == nullptr ) { 121return cpus ; 122 } 123 124while (dirent * entry = readdir (dir )) { 125size_t cpu = 0 ; 126if (parse_cpu_dir_name (entry -> d_name ,& cpu )) { 127cpus .push_back (cpu ); 128 } 129 } 130closedir (dir ); 131 132 std::sort (cpus .begin (),cpus .end ()); 133cpus .erase (std::unique (cpus .begin (),cpus .end ()),cpus .end ()); 134return cpus ; 135} 136#endif 137 138#if defined(__APPLE__ )&& defined(__aarch64__ ) 139static bool apple_sme_counted_perf_level (std::string name ) { 140for (std::string::size_type i = 0 ;i < name .size ();++ i ) { 141name [i ]= (char ) std::tolower ((unsigned char )name [i ]); 142 } 143 144// Conservative ceiling: only count perf-level names observed to provide full SME throughput. 145// Future names should be calibrated here before they raise the automatic SME thread cap. 146return name .find ("super" )!= std::string::npos || 147name .find ("performance" )!= std::string::npos ; 148} 149#endif 150 151static void add_smcus_from_smidr (uint64_t smidr ,size_t & num_private , std::map < uint32_t ,size_t > & shared_counts ) { 152// Arm ARM: SMIDR_EL1. SH==0 is implementation-defined; keep the existing 153// conservative policy and only treat zero affinity as private. 154const uint32_t sh = (uint32_t )((smidr >>13 )& 0x3 ); 155const uint32_t nsmc = (uint32_t )((smidr >>56 )& 0xF ); 156const size_t shared_count = nsmc == 0xF ?1 : (size_t )nsmc + 1 ; 157const uint32_t affinity = (uint32_t )(smidr & 0xFFFu ); 158const uint32_t affinity2 = (uint32_t )((smidr >>32 )& 0xFFFFFu ); 159const uint32_t id = (affinity2 <<12 ) |affinity ; 160 161if (nsmc == 0xF ) { 162GGML_LOG_WARN ("kleidiai: NSMC detected as 0xF indicating reseved value, setting min safe shared SMCU count to 1" ); 163 } 164 165switch (sh ) { 166case 2 :// private SMCU 167++ num_private ; 168break ; 169case 3 :// shared SMCU 170if (shared_counts [id ]< shared_count ) { 171shared_counts [id ]= shared_count ; 172 } 173break ; 174case 0 : 175if (id == 0 ) { 176++ num_private ; 177 }else if (shared_counts [id ]< shared_count ) { 178shared_counts [id ]= shared_count ; 179 } 180break ; 181default : 182break ; 183 } 184} 185 186static size_t detect_num_smcus () { 187const auto runtime_feat = ggml_feats_get_arch64_runtime (); 188if (!runtime_feat .has_sme ) { 189return 0 ; 190 } 191 192#if defined(__linux__ )&& defined(__aarch64__ ) 193// Linux/aarch64: Best-effort count of Streaming Mode Compute Units (SMCUs) via SMIDR_EL1 sysfs. 194size_t num_private = 0 ; 195 std::map < uint32_t ,size_t > shared_counts ; 196 197const std::vector < size_t > cpus = detect_cpu_ids (); 198for (const size_t cpu :cpus ) { 199const std::string path = 200"/sys/devices/system/cpu/cpu" + std::to_string (cpu )+ 201"/regs/identification/smidr_el1" ; 202 203 std::ifstream file (path ); 204if (!file .is_open ()) { 205continue ; 206 } 207 208uint64_t smidr = 0 ; 209if (!(file >> std::hex >>smidr )) { 210continue ; 211 } 212 213add_smcus_from_smidr (smidr ,num_private ,shared_counts ); 214 } 215 216size_t total = num_private ; 217for (const auto & entry :shared_counts ) { 218total += entry .second ; 219 } 220return total ; 221 222#elif defined(__APPLE__ )&& defined(__aarch64__ ) 223int perf_levels = 0 ; 224size_t size = sizeof (perf_levels ); 225if (sysctlbyname ("hw.nperflevels" ,& perf_levels ,& size ,nullptr ,0 )!= 0 || 226size != sizeof (perf_levels )|| perf_levels <=0 ) { 227return 0 ; 228 } 229 230size_t units = 0 ; 231for (int i = 0 ;i < perf_levels ;++ i ) { 232char key [64 ]= {}; 233int physical_cpus = 0 ; 234int cpus_per_l2 = 0 ; 235 236snprintf (key ,sizeof (key ),"hw.perflevel%d.physicalcpu" ,i ); 237size = sizeof (physical_cpus ); 238if (sysctlbyname (key ,& physical_cpus ,& size ,nullptr ,0 )!= 0 || 239size != sizeof (physical_cpus )|| physical_cpus <=0 ) { 240continue ; 241 } 242 243snprintf (key ,sizeof (key ),"hw.perflevel%d.cpusperl2" ,i ); 244size = sizeof (cpus_per_l2 ); 245if (sysctlbyname (key ,& cpus_per_l2 ,& size ,nullptr ,0 )!= 0 || 246size != sizeof (cpus_per_l2 )|| cpus_per_l2 <=0 ) { 247continue ; 248 } 249 250snprintf (key ,sizeof (key ),"hw.perflevel%d.name" ,i ); 251size = 0 ; 252if (sysctlbyname (key ,nullptr ,& size ,nullptr ,0 )!= 0 || size == 0 ) { 253continue ; 254 } 255 256 std::string name (size ,'\0' ); 257if (sysctlbyname (key ,& name [0 ],& size ,nullptr ,0 )!= 0 ) { 258continue ; 259 } 260name .resize (size ); 261while (!name .empty ()&& name .back ()== '\0' ) { 262name .pop_back (); 263 } 264 265if (apple_sme_counted_perf_level (name )) { 266units += (size_t ) ((physical_cpus + cpus_per_l2 - 1 ) /cpus_per_l2 ); 267 } 268 } 269 270return units ; 271 272#elif defined(_WIN32 )&& (defined(_M_ARM64 )|| defined(__aarch64__ )) 273// No verified Windows arm64 SMCU detection path yet. Return unknown and use 274// GGML_KLEIDIAI_SME=N as a diagnostics/debug override for SME thread cap 275// calibration until a detection mechanism is verified on real hardware. 276return 0 ; 277 278#else 279return 0 ; 280#endif 281} 282 283static int parse_uint_env (const char * s ,const char * name ,bool * ok ) { 284if (!s ) {* ok = false;return 0 ; } 285char * end = nullptr ; 286long v = strtol (s ,& end ,10 ); 287if (end == s || * end != '\0' ) { 288GGML_LOG_WARN ("kleidiai: invalid %s='%s' (expected integer)\n" ,name ,s ); 289* ok = false; 290return 0 ; 291 } 292if (v < 0 || v > INT_MAX ) { 293GGML_LOG_WARN ("kleidiai: out-of-range %s='%s'\n" ,name ,s ); 294* ok = false; 295return 0 ; 296 } 297* ok = true; 298return (int )v ; 299} 300 301static void init_kleidiai_context (void ) { 302ggml_critical_section_start (); 303static bool initialized = false; 304 305if (!initialized ) { 306initialized = true; 307 308// Optional diagnostics/debug overrides; production defaults come from runtime detection. 309const char * env_sme = getenv ("GGML_KLEIDIAI_SME" ); 310const char * env_threads = getenv ("GGML_TOTAL_THREADS" ); 311const char * env_chunk_mult = getenv ("GGML_KLEIDIAI_CHUNK_MULTIPLIER" ); 312 313const auto runtime_feat = ggml_feats_get_arch64_runtime (); 314 315size_t detected_smcus = 0 ; 316 317ctx .features = (runtime_feat .has_dotprod ?CPU_FEATURE_DOTPROD :CPU_FEATURE_NONE ) | 318 (runtime_feat .has_i8mm ?CPU_FEATURE_I8MM :CPU_FEATURE_NONE ) | 319 (runtime_feat .has_fp16 ?CPU_FEATURE_FP16 :CPU_FEATURE_NONE ) | 320 (runtime_feat .sve_cnt == QK8_0 ?CPU_FEATURE_SVE :CPU_FEATURE_NONE ); 321 322if (env_threads ) { 323bool ok = false; 324int hint = parse_uint_env (env_threads ,"GGML_TOTAL_THREADS" ,& ok ); 325if (ok && hint > 0 ) { 326ctx .thread_hint = hint ; 327 } 328 } 329 330if (env_chunk_mult ) { 331bool ok = false; 332int multiplier = parse_uint_env (env_chunk_mult ,"GGML_KLEIDIAI_CHUNK_MULTIPLIER" ,& ok ); 333if (ok && multiplier > 0 ) { 334ctx .chunk_multiplier = multiplier ; 335 } 336 } 337 338int sme_cores = 0 ; 339bool sme_env_ok = false; 340bool sme_env_set = (env_sme != nullptr ); 341 342const bool has_supported_sme_family = runtime_feat .has_sme ; 343bool sme_cap_detected = false; 344 345if (has_supported_sme_family ) { 346detected_smcus = detect_num_smcus (); 347sme_cap_detected = detected_smcus > 0 ; 348// Some platforms expose SME without exposing a calibrated SMCU count. 349// Use one SME thread as the conservative default; add platform SMCU detection to raise it. 350sme_cores = sme_cap_detected ? (int )detected_smcus :1 ; 351 352if (!sme_env_set && !sme_cap_detected ) { 353GGML_LOG_INFO ("kleidiai: SME detected; SMCU count unavailable, using conservative SME thread cap=1\n" ); 354 } 355 } 356 357// Runtime-detect SME support and available SMCUs first. The detected SMCU 358// count is used as the SME thread cap, and GGML_KLEIDIAI_SME can debug-override that: 359// - unset: use runtime detection. 360// - 0: disable SME-family kernels. 361// - N > 0: use N as the SME thread cap, if an SME-family kernel is selectable. 362if (sme_env_set ) { 363bool ok = false; 364int v = parse_uint_env (env_sme ,"GGML_KLEIDIAI_SME" ,& ok ); 365sme_env_ok = ok ; 366 367if (ok ) { 368if (has_supported_sme_family ) { 369sme_cores = v ; 370 }else { 371if (v > 0 ) { 372GGML_LOG_WARN ("kleidiai: GGML_KLEIDIAI_SME=%d but SME is not supported on this CPU; disabling SME-family kernels\n" ,v ); 373 } 374sme_cores = 0 ; 375 } 376 }else { 377GGML_LOG_WARN ("kleidiai: GGML_KLEIDIAI_SME set but parsing failed; using automatic SME thread cap\n" ); 378 } 379 } 380 381if (sme_cores > 0 && has_supported_sme_family ) { 382ctx .features |=CPU_FEATURE_SME ; 383if (runtime_feat .has_sme2 ) { 384ctx .features |=CPU_FEATURE_SME2 ; 385 } 386 } 387 388// Kernel selection 389ctx .kernels_q4 = ggml_kleidiai_select_kernels_q4_0 (ctx .features ); 390ctx .kernels_q8 = ggml_kleidiai_select_kernels_q8_0 (ctx .features ); 391ctx .kernels_f32 = ggml_kleidiai_select_kernels_f32 (ctx .features ); 392 393if (!ctx .kernels_q4 ) { 394GGML_LOG_INFO ("kleidiai: no compatible q4 kernels found for CPU features mask %d\n" , (int )ctx .features ); 395 }else { 396GGML_LOG_INFO ("kleidiai: primary q4 kernel feature %s\n" ,cpu_feature_to_string (ctx .kernels_q4 -> required_cpu )); 397 } 398 399if (!ctx .kernels_q8 ) { 400GGML_LOG_INFO ("kleidiai: no compatible q8 kernels found for CPU features mask %d\n" , (int )ctx .features ); 401 }else { 402GGML_LOG_INFO ("kleidiai: primary q8 kernel feature %s\n" ,cpu_feature_to_string (ctx .kernels_q8 -> required_cpu )); 403 } 404 405if (!ctx .kernels_f32 ) { 406GGML_LOG_INFO ("kleidiai: no compatible f32 kernels found for CPU features mask %d\n" , (int )ctx .features ); 407 }else { 408GGML_LOG_INFO ("kleidiai: primary f32 kernel feature %s\n" ,cpu_feature_to_string (ctx .kernels_f32 -> required_cpu )); 409 } 410 411const bool has_selected_sme_family_kernel = 412 (ctx .kernels_q4 && is_sme_family (ctx .kernels_q4 -> required_cpu ))|| 413 (ctx .kernels_q8 && is_sme_family (ctx .kernels_q8 -> required_cpu ))|| 414 (ctx .kernels_f32 && is_sme_family (ctx .kernels_f32 -> required_cpu )); 415ctx .sme_thread_cap = has_selected_sme_family_kernel ?sme_cores :0 ; 416 417if (has_selected_sme_family_kernel ) { 418if (sme_env_set && sme_env_ok && sme_cores > 0 ) { 419GGML_LOG_INFO ("kleidiai: SME enabled (GGML_KLEIDIAI_SME=%d debug override)\n" ,sme_cores ); 420 }else if (sme_cap_detected ) { 421GGML_LOG_INFO ("kleidiai: SME enabled (runtime-detected SME thread cap=%d)\n" ,sme_cores ); 422 }else { 423GGML_LOG_INFO ("kleidiai: SME enabled (runtime SME detected, conservative thread cap=%d)\n" ,sme_cores ); 424 } 425 }else { 426GGML_LOG_INFO ("kleidiai: SME disabled\n" ); 427 } 428 } 429 430ggml_critical_section_end (); 431} 432 433static inline int kleidiai_sme_thread_cap () { 434return ctx .sme_thread_cap ; 435} 436 437static inline size_t align_up (size_t value ,size_t alignment ) { 438if (alignment == 0 ) { 439return value ; 440 } 441const size_t remainder = value %alignment ; 442return remainder == 0 ?value :value + (alignment - remainder ); 443} 444 445static inline size_t gcd_size (size_t a ,size_t b ) { 446while (b != 0 ) { 447const size_t t = a %b ; 448a = b ; 449b = t ; 450 } 451return a ; 452} 453 454static inline bool lcm_size (size_t a ,size_t b ,size_t & result ) { 455if (a == 0 || b == 0 ) { 456result = 0 ; 457return false; 458 } 459const size_t g = gcd_size (a ,b ); 460const size_t q = a /g ; 461if (q > SIZE_MAX /b ) { 462return false; 463 } 464result = q * b ; 465return true; 466} 467 468static inline size_t ceil_div_size (size_t a ,size_t b ) { 469return b == 0 ?0 : (a + b - 1 ) /b ; 470} 471 472static inline size_t kleidiai_chunk_cols (size_t n ,int nth_total ,bool disable_chunking ,size_t n_step ) { 473const size_t multiplier = (nth_total == 1 || disable_chunking ) ?1 : std::max < size_t > (1 , (size_t )ctx .chunk_multiplier ); 474const size_t divisor = std::max < size_t > (1 , (size_t )nth_total * multiplier ); 475const size_t chunk_cols = align_up (std::max < size_t > (1 ,ceil_div_size (n ,divisor )),n_step ); 476return chunk_cols ?chunk_cols :n_step ; 477} 478 479struct kleidiai_block_args { 480size_t lhs_bl ; 481size_t rhs_bl ; 482size_t pack_bl ; 483}; 484 485static inline kleidiai_block_args kleidiai_get_block_args (ggml_type rhs_type ) { 486switch (rhs_type ) { 487case GGML_TYPE_Q4_0 : 488return {QK4_0 ,QK4_0 ,QK4_0 }; 489case GGML_TYPE_Q8_0 : 490return {0 ,0 ,QK8_0 }; 491default : 492return {0 ,0 ,0 }; 493 } 494} 495 496static inline bool kleidiai_pack_fallback_allowed () { 497if (ctx .sme_thread_cap <=0 ) { 498return false; 499 } 500if (ctx .thread_hint <=0 ) { 501return true; 502 } 503return ctx .thread_hint > ctx .sme_thread_cap ; 504} 505 506struct kleidiai_weight_header { 507uint32_t magic ; 508uint16_t version ; 509uint16_t slot_count ; 510uint64_t offsets [GGML_KLEIDIAI_MAX_KERNEL_SLOTS ]; 511uint64_t sizes [GGML_KLEIDIAI_MAX_KERNEL_SLOTS ]; 512}; 513 514static inline kleidiai_weight_header * kleidiai_weight_header_from_ptr (void * data ) { 515return reinterpret_cast < kleidiai_weight_header *> (data ); 516} 517 518static inline const kleidiai_weight_header * kleidiai_weight_header_from_ptr (const void * data ) { 519return reinterpret_cast < const kleidiai_weight_header *> (data ); 520} 521 522static inline bool kleidiai_is_weight_header_valid (const kleidiai_weight_header * header ) { 523if (!header ) { 524return false; 525 } 526if (header -> magic != GGML_KLEIDIAI_PACK_MAGIC || header -> version != GGML_KLEIDIAI_PACK_VERSION ) { 527return false; 528 } 529if (header -> slot_count == 0 || header -> slot_count > GGML_KLEIDIAI_MAX_KERNEL_SLOTS ) { 530return false; 531 } 532return true; 533} 534 535static inline uint8_t * kleidiai_weight_slot_ptr (kleidiai_weight_header * header ,int slot ) { 536if (!kleidiai_is_weight_header_valid (header )) { 537return nullptr ; 538 } 539if (slot < 0 || slot >=header -> slot_count ) { 540return nullptr ; 541 } 542return reinterpret_cast < uint8_t *> (header )+ header -> offsets [slot ]; 543} 544 545static inline const uint8_t * kleidiai_weight_slot_ptr (const kleidiai_weight_header * header ,int slot ) { 546if (!kleidiai_is_weight_header_valid (header )) { 547return nullptr ; 548 } 549if (slot < 0 || slot >=header -> slot_count ) { 550return nullptr ; 551 } 552return reinterpret_cast < const uint8_t *> (header )+ header -> offsets [slot ]; 553} 554 555static inline ggml_kleidiai_kernels * kleidiai_primary_kernel_q4 () { 556return ctx .kernels_q4 ; 557} 558 559static inline ggml_kleidiai_kernels * kleidiai_primary_kernel_q8 () { 560return ctx .kernels_q8 ; 561} 562 563static inline ggml_kleidiai_kernels * kleidiai_primary_kernel_f32 () { 564return ctx .kernels_f32 ; 565} 566 567template < typename SelectFallback > 568static int kleidiai_collect_kernel_chain_common ( 569ggml_kleidiai_kernels * primary , 570cpu_feature features , 571 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > & out , 572SelectFallback select_fallback ) { 573int count = 0 ; 574if (!primary ) { 575return 0 ; 576 } 577out [count ++ ]= primary ; 578 579if (primary -> rhs_info .repack_mode == RHS_REPACK_SINGLE_ONLY ) { 580return count ; 581 } 582 583if (is_sme_family (primary -> required_cpu )) { 584const cpu_feature fallback_mask = static_cast < cpu_feature > (features & ~(CPU_FEATURE_SME |CPU_FEATURE_SME2 )); 585if (fallback_mask != CPU_FEATURE_NONE ) { 586ggml_kleidiai_kernels * fallback = select_fallback (fallback_mask ); 587if (fallback && fallback != primary && 588fallback -> rhs_info .repack_mode != RHS_REPACK_SINGLE_ONLY && 589fallback -> lhs_type == primary -> lhs_type && 590fallback -> rhs_type == primary -> rhs_type && 591fallback -> op_type == primary -> op_type ) { 592out [count ++ ]= fallback ; 593 } 594 } 595 } 596 597return count ; 598} 599 600static int kleidiai_collect_kernel_chain (const struct ggml_tensor * op , 601 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > & out ) { 602ggml_kleidiai_kernels * primary = ggml_kleidiai_select_kernels (ctx .features ,op ); 603return kleidiai_collect_kernel_chain_common (primary ,ctx .features ,out , 604 [& ](cpu_feature mask ) {return ggml_kleidiai_select_kernels (mask ,op ); }); 605} 606 607static int kleidiai_collect_q4_chain (std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > & out ) { 608ggml_kleidiai_kernels * primary = kleidiai_primary_kernel_q4 (); 609return kleidiai_collect_kernel_chain_common (primary ,ctx .features ,out , 610 [& ](cpu_feature mask ) {return ggml_kleidiai_select_kernels_q4_0 (mask ); }); 611} 612 613static int kleidiai_collect_q8_chain (std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > & out ) { 614ggml_kleidiai_kernels * primary = kleidiai_primary_kernel_q8 (); 615return kleidiai_collect_kernel_chain_common (primary ,ctx .features ,out , 616 [& ](cpu_feature mask ) {return ggml_kleidiai_select_kernels_q8_0 (mask ); }); 617} 618 619static int kleidiai_collect_f32_chain (std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > & out ) { 620ggml_kleidiai_kernels * primary = kleidiai_primary_kernel_f32 (); 621return kleidiai_collect_kernel_chain_common (primary ,ctx .features ,out , 622 [& ](cpu_feature mask ) {return ggml_kleidiai_select_kernels_f32 (mask ); }); 623} 624 625static inline int64_t ggml_ne (const ggml_tensor * tensor ,int dim ) { 626GGML_ASSERT (dim >=0 && dim < GGML_MAX_DIMS ); 627return tensor -> ne [dim ]; 628} 629 630namespace ggml::cpu::kleidiai { 631 632static size_t round_down (size_t x ,size_t y ) { 633return y == 0 ?x :x - (x %y ); 634} 635 636static void transpose_f32kxn_f16nxk (size_t n ,size_t k ,float * dst ,const uint16_t * src ,size_t rhs_stride ) { 637size_t src_stride = rhs_stride /sizeof (uint16_t ); 638size_t dst_stride = n ; 639 640for (size_t k_idx = 0 ;k_idx < k ;++ k_idx ) { 641for (size_t n_idx = 0 ;n_idx < n ;++ n_idx ) { 642uint16_t v = * (src + k_idx + n_idx * src_stride ); 643* (dst + n_idx + k_idx * dst_stride )= kai_cast_f32_f16 (v ); 644 } 645 } 646} 647 648class tensor_traits :public ggml::cpu::tensor_traits { 649bool work_size (int /* n_threads */ ,const struct ggml_tensor * op ,size_t & size )override { 650if (op -> op != GGML_OP_MUL_MAT ) { 651return false; 652 } 653 654 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 655const int slot_count = kleidiai_collect_kernel_chain (op ,kernel_chain ); 656if (slot_count == 0 ) { 657return false; 658 } 659 660const bool is_gemv = op -> src [1 ]-> ne [1 ]== 1 ; 661const size_t k = op -> src [0 ]-> ne [0 ]; 662const size_t n = op -> src [0 ]-> ne [1 ]; 663const size_t m = op -> src [1 ]-> ne [1 ]; 664 665if (op -> src [0 ]-> type == GGML_TYPE_Q4_0 || op -> src [0 ]-> type == GGML_TYPE_Q8_0 ) { 666const size_t qk = (op -> src [0 ]-> type == GGML_TYPE_Q4_0 ) ?QK4_0 :QK8_0 ; 667 668size_t cursor = 0 ; 669bool any_slot = false; 670 671for (int slot = 0 ;slot < slot_count ;++ slot ) { 672ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 673lhs_packing_info * lhs_info = is_gemv ?& kernels -> gemv_lhs_info :& kernels -> gemm_lhs_info ; 674kernel_info * kernel = is_gemv ?& kernels -> gemv :& kernels -> gemm ; 675 676if (!lhs_info || !lhs_info -> packed_size_ex || !kernel ) { 677return false; 678 } 679 680const size_t mr = kernel -> get_mr (); 681const size_t kr = kernel -> get_kr (); 682const size_t sr = kernel -> get_sr (); 683 684const size_t packed = lhs_info -> packed_size_ex (m ,k ,qk ,mr ,kr ,sr ); 685 686cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 687cursor += packed ; 688any_slot = true; 689 } 690 691if (!any_slot ) { 692return false; 693 } 694 695size = cursor ; 696return true; 697 } 698 699if (op -> src [0 ]-> type == GGML_TYPE_F32 ) { 700ggml_kleidiai_kernels * primary = kernel_chain [0 ]; 701kernel_info * gemv_kernel = primary ?& primary -> gemv :nullptr ; 702if (is_gemv && op -> src [1 ]-> nb [0 ]== (int64_t )sizeof (float )&& gemv_kernel && 703gemv_kernel -> get_lhs_offset_ex && gemv_kernel -> get_rhs_packed_offset_ex && 704gemv_kernel -> run_kernel_ex && gemv_kernel -> get_dst_offset ) { 705size = 0 ; 706return true; 707 } 708 709size_t cursor = 0 ; 710bool any_slot = false; 711 712for (int slot = 0 ;slot < slot_count ;++ slot ) { 713ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 714lhs_packing_info * lhs_info = & kernels -> gemm_lhs_info ; 715kernel_info * kernel = & kernels -> gemm ; 716 717if (!lhs_info || !lhs_info -> packed_size_ex || !kernel ) { 718return false; 719 } 720 721const size_t mr = kernel -> get_mr (); 722const size_t kr = kernel -> get_kr (); 723const size_t sr = kernel -> get_sr (); 724 725cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 726cursor += lhs_info -> packed_size_ex (m ,k ,0 ,mr ,kr ,sr ); 727any_slot = true; 728 } 729 730if (!any_slot ) { 731return false; 732 } 733 734size = cursor ; 735return true; 736 } 737 738if (op -> src [0 ]-> type == GGML_TYPE_F16 ) { 739const int64_t lhs_batch_size0 = op -> src [1 ]-> ne [2 ]; 740const int64_t rhs_batch_size0 = op -> src [0 ]-> ne [2 ]; 741GGML_ASSERT (rhs_batch_size0 > 0 ); 742const int64_t r = lhs_batch_size0 /rhs_batch_size0 ; 743 744size_t cursor = 0 ; 745bool any_slot = false; 746 747for (int slot = 0 ;slot < slot_count ;++ slot ) { 748ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 749lhs_packing_info * lhs_info = is_gemv ?& kernels -> gemv_lhs_info :& kernels -> gemm_lhs_info ; 750kernel_info * kernel = is_gemv ?& kernels -> gemv :& kernels -> gemm ; 751if (!lhs_info || !lhs_info -> packed_size_ex || !kernels -> rhs_info .packed_size_ex || !kernel ) { 752return false; 753 } 754 755const size_t mr = kernel -> get_mr (); 756const size_t kr = kernel -> get_kr (); 757const size_t sr = kernel -> get_sr (); 758 759cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 760cursor += lhs_info -> packed_size_ex (m * r ,k ,0 ,mr ,kr ,sr ); 761any_slot = true; 762 } 763 764for (int slot = 0 ;slot < slot_count ;++ slot ) { 765ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 766kernel_info * kernel = is_gemv ?& kernels -> gemv :& kernels -> gemm ; 767if (!kernel || !kernels -> rhs_info .packed_size_ex ) { 768return false; 769 } 770cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 771cursor += kernels -> rhs_info .packed_size_ex (n ,k ,kernel -> get_nr (),kernel -> get_kr (),0 ); 772 } 773 774cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 775cursor += k * n * sizeof (float ); 776cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 777cursor += n * sizeof (float ); 778 779if (!any_slot ) { 780return false; 781 } 782 783size = cursor ; 784return true; 785 } 786 787return false; 788 } 789 790bool compute_forward (struct ggml_compute_params * params ,struct ggml_tensor * dst )override { 791if (dst -> op == GGML_OP_MUL_MAT ) { 792if (dst -> src [0 ]-> type == GGML_TYPE_Q4_0 || dst -> src [0 ]-> type == GGML_TYPE_Q8_0 ) { 793return compute_forward_qx (params ,dst ); 794 }else if (dst -> src [0 ]-> type == GGML_TYPE_F32 ) { 795return compute_forward_f32 (params ,dst ); 796 }else if (dst -> src [0 ]-> type == GGML_TYPE_F16 ) { 797return compute_forward_fp16 (params ,dst ); 798 } 799 }else if (dst -> op == GGML_OP_GET_ROWS ) { 800if (dst -> src [0 ]-> type == GGML_TYPE_Q4_0 || dst -> src [0 ]-> type == GGML_TYPE_Q8_0 ) { 801return compute_forward_get_rows (params ,dst ); 802 } 803 } 804return false; 805 } 806 807bool compute_forward_f32 (ggml_compute_params * params ,struct ggml_tensor * dst ) { 808GGML_ASSERT (dst -> src [0 ]-> type == GGML_TYPE_F32 ); 809 810const ggml_tensor * src0 = dst -> src [0 ]; 811const ggml_tensor * src1 = dst -> src [1 ]; 812 813GGML_TENSOR_BINARY_OP_LOCALS 814 815if (src1 -> type != GGML_TYPE_F32 || dst -> type != GGML_TYPE_F32 ) { 816return false; 817 } 818 819ggml_kleidiai_kernels * kernels = kleidiai_primary_kernel_f32 (); 820if (!kernels ) { 821return false; 822 } 823 824const size_t k = ne00 ; 825const size_t m = ne11 ; 826const size_t n = ne01 ; 827const bool use_gemv = m == 1 && src1 -> nb [0 ]== (int64_t )sizeof (float )&& 828kernels -> gemv .get_lhs_offset_ex && 829kernels -> gemv .get_rhs_packed_offset_ex && 830kernels -> gemv .run_kernel_ex && 831kernels -> gemv .get_dst_offset ; 832 833kernel_info * kernel = use_gemv ?& kernels -> gemv :& kernels -> gemm ; 834lhs_packing_info * lhs_info = & kernels -> gemm_lhs_info ; 835 836if (!kernel || !kernel -> get_lhs_offset_ex || 837 !kernel -> get_rhs_packed_offset_ex || !kernel -> run_kernel_ex || !kernel -> get_dst_offset ) { 838return false; 839 } 840 841if (!use_gemv && (!lhs_info || !lhs_info -> get_offset || !lhs_info -> get_packed_offset_ex || 842 !lhs_info -> packed_size_ex || !lhs_info -> pack_func_ex )) { 843return false; 844 } 845 846const kleidiai_weight_header * header = kleidiai_weight_header_from_ptr (src0 -> data ); 847const bool has_header = kleidiai_is_weight_header_valid (header ); 848 849const uint8_t * rhs_base = has_header ?kleidiai_weight_slot_ptr (header ,0 ) 850 :static_cast < const uint8_t *> (src0 -> data ); 851if (!rhs_base ) { 852return false; 853 } 854 855const int nth = params -> nth > 0 ?params -> nth :1 ; 856const int ith = params -> ith ; 857 858const size_t mr = kernel -> get_mr (); 859const size_t kr = kernel -> get_kr (); 860const size_t sr = kernel -> get_sr (); 861 862const size_t lhs_packed_size = use_gemv ?0 :lhs_info -> packed_size_ex (m ,k ,0 ,mr ,kr ,sr ); 863if (!use_gemv ) { 864GGML_ASSERT (lhs_packed_size <=params -> wsize ); 865 } 866 867uint8_t * lhs_packed = static_cast < uint8_t *> (params -> wdata ); 868const size_t dst_stride = dst -> nb [1 ]; 869const size_t n_step = kernel -> get_n_step () ?kernel -> get_n_step () :1 ; 870const bool disable_chunking = ggml_is_numa (); 871GGML_ASSERT (n <= (size_t )INT_MAX ); 872 873for (int64_t batch_idx = 0 ;batch_idx < ne12 ;++ batch_idx ) { 874const uint8_t * lhs_batch_base = static_cast < const uint8_t *> (src1 -> data )+ batch_idx * src1 -> nb [2 ]; 875uint8_t * dst_batch_base = static_cast < uint8_t *> (dst -> data )+ batch_idx * dst -> nb [2 ]; 876 877if (!use_gemv ) { 878const int64_t m_roundup_mr = kai_roundup ((int64_t )m , (int64_t )mr ); 879int64_t max_threads = mr ? (m_roundup_mr / (int64_t )mr ) :nth ; 880max_threads = std::max < int64_t > (1 ,max_threads ); 881const int64_t use_threads = std::min < int64_t > (nth ,max_threads ); 882 883if (ith < use_threads ) { 884const int64_t num_m_per_thread0 = round_down ((size_t )(m_roundup_mr /use_threads ),mr ); 885const int64_t num_m_per_threadN_1 = (int64_t )m - (use_threads - 1 )* num_m_per_thread0 ; 886 887const int64_t m_start = (int64_t )ith * num_m_per_thread0 ; 888const int64_t m_count = (ith == use_threads - 1 ) ?num_m_per_threadN_1 :num_m_per_thread0 ; 889 890const size_t base_packed_off = lhs_info -> get_packed_offset_ex (m_start ,k ,0 ,mr ,kr ,sr ); 891const size_t next_block_off = lhs_info -> get_packed_offset_ex (m_start + mr ,k ,0 ,mr ,kr ,sr ); 892const size_t row_stride_bytes = mr ? (next_block_off - base_packed_off ) /mr :0 ; 893 894int64_t remaining = m_count ; 895int64_t cur = m_start ; 896 897while (remaining > 0 ) { 898const int64_t take = std::min < int64_t > ((int64_t )m - cur ,remaining ); 899const size_t src_off = lhs_info -> get_offset (cur ,src1 -> nb [1 ]); 900const void * src_ptr = lhs_batch_base + src_off ; 901const size_t dst_off = base_packed_off + (size_t )(cur - m_start )* row_stride_bytes ; 902void * dst_ptr = lhs_packed + dst_off ; 903 904lhs_info -> pack_func_ex (take ,k ,0 ,mr ,kr ,sr ,0 ,src_ptr ,src1 -> nb [1 ],dst_ptr ); 905 906cur += take ; 907remaining -= take ; 908 } 909 } 910 } 911 912if (ith == 0 ) { 913ggml_threadpool_chunk_set (params -> threadpool ,0 ); 914 } 915 916ggml_barrier (params -> threadpool ); 917 918const size_t chunk_cols = kleidiai_chunk_cols (n ,nth ,disable_chunking ,n_step ); 919GGML_ASSERT (chunk_cols <= (size_t )INT_MAX ); 920 921int current_col = ggml_threadpool_chunk_add (params -> threadpool , (int )chunk_cols ); 922while ((size_t )current_col < n ) { 923const size_t n_start = (size_t )current_col ; 924const size_t n_to_process = std::min (chunk_cols ,n - n_start ); 925 926if (n_to_process > 0 ) { 927const size_t lhs_offset = use_gemv ?kernel -> get_lhs_offset_ex (0 ,k ,0 ) 928 :lhs_info -> get_packed_offset_ex (0 ,k ,0 ,mr ,kr ,sr ); 929const size_t rhs_packed_offset = kernel -> get_rhs_packed_offset_ex (n_start ,k ,0 ); 930const size_t dst_offset = kernel -> get_dst_offset (0 ,n_start ,dst_stride ); 931 932const void * lhs_ptr = use_gemv ?lhs_batch_base + lhs_offset 933 :lhs_packed + lhs_offset ; 934const void * rhs_ptr = rhs_base + rhs_packed_offset ; 935float * dst_ptr = reinterpret_cast < float *> (dst_batch_base + dst_offset ); 936 937kernel -> run_kernel_ex (m ,n_to_process ,k ,use_gemv ?src1 -> nb [1 ] :0 , 938lhs_ptr , 939rhs_ptr , 940dst_ptr , 941dst_stride , 942sizeof (float ), 943- FLT_MAX , 944FLT_MAX ); 945 } 946 947current_col = ggml_threadpool_chunk_add (params -> threadpool , (int )chunk_cols ); 948 } 949 950if (batch_idx != ne12 - 1 ) { 951ggml_barrier (params -> threadpool ); 952 } 953 } 954 955return true; 956 } 957 958bool compute_forward_fp16 (ggml_compute_params * params ,struct ggml_tensor * dst ) { 959const ggml_tensor * src0 = dst -> src [0 ]; 960const ggml_tensor * src1 = dst -> src [1 ]; 961 962GGML_TENSOR_BINARY_OP_LOCALS 963 964 ggml_kleidiai_kernels* kernels = ggml_kleidiai_select_kernels (ctx .features ,dst ); 965if (!kernels ) { 966return false; 967 } 968 969const bool is_gemv = src1 -> ne [1 ]== 1 ; 970kernel_info * kernel = is_gemv ?& kernels -> gemv :& kernels -> gemm ; 971lhs_packing_info * lhs_info = is_gemv ?& kernels -> gemv_lhs_info :& kernels -> gemm_lhs_info ; 972GGML_ASSERT (kernel ); 973if (!kernels -> rhs_info .pack_func_ex || 974 !kernel -> get_lhs_offset_ex || !kernel -> get_rhs_packed_offset_ex || !kernel -> run_kernel_ex ) { 975return false; 976 } 977 978const int nth = params -> nth ; 979const int ith = params -> ith ; 980 981const int64_t lhs_batch_size0 = ne12 ; 982const int64_t rhs_batch_size0 = ne02 ; 983const int64_t batch_size = lhs_batch_size0 ; 984 985GGML_ASSERT (rhs_batch_size0 > 0 ); 986GGML_ASSERT (lhs_batch_size0 %rhs_batch_size0 == 0 ); 987const int64_t r = lhs_batch_size0 /rhs_batch_size0 ; 988 989const int64_t m_group = ne11 ; 990const int64_t m = m_group ; 991const int64_t n = ne01 ; 992const int64_t k = ne00 ; 993 994const size_t lhs_stride = src1 -> nb [1 ]; 995const size_t rhs_stride = src0 -> nb [1 ]; 996const size_t dst_stride = dst -> nb [1 ]; 997 998const int64_t mr = (int64_t )kernel -> get_mr (); 999const int64_t nr = (int64_t )kernel -> get_nr (); 1000const int64_t kr = (int64_t )kernel -> get_kr (); 1001const int64_t sr = (int64_t )kernel -> get_sr (); 1002 1003const size_t lhs_packed_size = lhs_info -> packed_size_ex (m ,k ,0 ,mr ,kr ,sr ); 1004const size_t rhs_packed_size = kernels -> rhs_info .packed_size_ex (n ,k ,nr ,kr ,0 ); 1005const size_t kxn_size = k * n * sizeof (float ); 1006const size_t bias_size = n * sizeof (float ); 1007 1008const size_t wsize_required = lhs_packed_size + rhs_packed_size + kxn_size + bias_size ; 1009GGML_ASSERT (wsize_required <=params -> wsize ); 1010 1011uint8_t * lhs_packed = static_cast < uint8_t *> (params -> wdata ); 1012uint8_t * rhs_packed = lhs_packed + lhs_packed_size ; 1013uint8_t * rhs_kxn = rhs_packed + rhs_packed_size ; 1014uint8_t * bias = rhs_kxn + kxn_size ; 1015 1016for (int64_t batch_idx = 0 ;batch_idx < batch_size ;++ batch_idx ) { 1017const int64_t rhs_batch_idx = batch_idx /r ; 1018const uint8_t * rhs_batch_base = static_cast < const uint8_t *> (src0 -> data )+ rhs_batch_idx * src0 -> nb [2 ]; 1019uint8_t * dst_batch_base = static_cast < uint8_t *> (dst -> data )+ batch_idx * dst -> nb [2 ]; 1020 1021// LHS packing (threaded over m, honoring mr alignment and KV groups) 1022 { 1023const int64_t m_roundup_mr = kai_roundup (m ,mr ); 1024const int64_t num_threads = KAI_MIN (m_roundup_mr /mr ,nth ); 1025 1026if (ith < num_threads ) { 1027const int64_t num_m_per_thread0 = round_down ((size_t )(m_roundup_mr /num_threads ), (size_t )mr ); 1028const int64_t num_m_per_threadN_1 = m - (num_threads - 1 )* num_m_per_thread0 ; 1029 1030const int64_t m_start = ith * num_m_per_thread0 ; 1031const int64_t m_count = (ith == num_threads - 1 ) ?num_m_per_threadN_1 :num_m_per_thread0 ; 1032 1033// Base packed offset (aligned) and per-row stride in bytes 1034const size_t base_packed_off = lhs_info -> get_packed_offset_ex (m_start ,k ,0 ,mr ,kr ,sr ); 1035const size_t next_block_off = lhs_info -> get_packed_offset_ex (m_start + mr ,k ,0 ,mr ,kr ,sr ); 1036const size_t row_stride_bytes = (next_block_off - base_packed_off ) / (size_t )mr ; 1037 1038int64_t remaining = m_count ; 1039int64_t cur = m_start ; 1040 1041while (remaining > 0 ) { 1042const int64_t row_in_group = cur ; 1043const int64_t avail = m_group - row_in_group ; 1044const int64_t take = std::min (avail ,remaining ); 1045 1046const uint8_t * lhs_batch_base = static_cast < const uint8_t *> (src1 -> data )+ batch_idx * src1 -> nb [2 ]; 1047const void * src_ptr = lhs_batch_base + (size_t )row_in_group * lhs_stride ; 1048const size_t dst_off = base_packed_off + (size_t )(cur - m_start )* row_stride_bytes ; 1049void * dst_ptr = lhs_packed + dst_off ; 1050 1051lhs_info -> pack_func_ex (take ,k ,0 ,mr ,kr ,sr ,0 ,src_ptr ,lhs_stride ,dst_ptr ); 1052 1053cur += take ; 1054remaining -= take ; 1055 } 1056 } 1057 } 1058 1059// RHS packing (single thread), then synchronize 1060if (ith == 0 ) { 1061memset (bias ,0 , (size_t )n * sizeof (float )); 1062transpose_f32kxn_f16nxk ((size_t )n , (size_t )k , 1063reinterpret_cast < float *> (rhs_kxn ), 1064reinterpret_cast < const uint16_t *> (rhs_batch_base ), 1065rhs_stride ); 1066 1067kernels -> rhs_info .pack_func_ex (1 ,n ,k ,nr ,kr ,sr ,0 ,n * sizeof (float ), 1068rhs_kxn ,bias ,nullptr ,rhs_packed ,0 ,nullptr ); 1069 } 1070 1071ggml_barrier (params -> threadpool ); 1072 1073// Matmul (threaded over n) 1074 { 1075const int64_t n_step = (int64_t )kernel -> get_n_step (); 1076int64_t num_threads_n = KAI_MIN (n /n_step ,nth ); 1077if (num_threads_n <=0 ) { 1078num_threads_n = 1 ; 1079 } 1080 1081if (ith < num_threads_n ) { 1082const int64_t num_n_per_thread0 = round_down ((size_t )(n /num_threads_n ), (size_t )n_step ); 1083const int64_t num_n_per_threadN_1 = n - (num_threads_n - 1 )* num_n_per_thread0 ; 1084 1085const int64_t n_start = ith * num_n_per_thread0 ; 1086const int64_t n_to_process = (ith == num_threads_n - 1 ) ?num_n_per_threadN_1 :num_n_per_thread0 ; 1087 1088// LHS packed base at row 0 (consistent with packing above) 1089const size_t lhs_packed_offset0 = lhs_info -> get_packed_offset_ex (0 ,k ,0 ,mr ,kr ,sr ); 1090const size_t rhs_packed_offset = kernel -> get_rhs_packed_offset_ex (n_start ,k ,0 ); 1091const size_t dst_offset = kernel -> get_dst_offset ((size_t )0 , (size_t )n_start ,dst_stride ); 1092 1093const void * lhs_ptr = lhs_packed + lhs_packed_offset0 ; 1094const void * rhs_ptr = rhs_packed + rhs_packed_offset ; 1095float * dst_ptr = reinterpret_cast < float *> (dst_batch_base + dst_offset ); 1096 1097kernel -> run_kernel_ex (m ,n_to_process ,k ,0 ,lhs_ptr ,rhs_ptr ,dst_ptr ,dst_stride ,sizeof (float ),- FLT_MAX ,FLT_MAX ); 1098 } 1099 } 1100 1101if (batch_idx != batch_size - 1 ) { 1102ggml_barrier (params -> threadpool ); 1103 } 1104 } 1105 1106return true; 1107 } 1108 1109bool compute_forward_qx (struct ggml_compute_params * params ,struct ggml_tensor * dst ) { 1110GGML_ASSERT (dst -> src [0 ]-> type == GGML_TYPE_Q4_0 || dst -> src [0 ]-> type == GGML_TYPE_Q8_0 ); 1111 1112const ggml_tensor * src0 = dst -> src [0 ]; 1113const ggml_tensor * src1 = dst -> src [1 ]; 1114 1115GGML_TENSOR_BINARY_OP_LOCALS 1116 1117const kleidiai_weight_header* header = kleidiai_weight_header_from_ptr (src0 -> data ); 1118const bool has_header = kleidiai_is_weight_header_valid (header ); 1119const bool is_gemv = src1 -> ne [1 ]== 1 ; 1120 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 1121const int slot_total = kleidiai_collect_kernel_chain (dst ,kernel_chain ); 1122 1123auto weight_for_slot = [& ](int slot_index ,size_t & size_out )-> const uint8_t * { 1124if (slot_index < 0 || slot_index >=slot_total ) { 1125return nullptr ; 1126 } 1127if (has_header ) { 1128if (slot_index < header -> slot_count ) { 1129size_out = static_cast < size_t > (header -> sizes [slot_index ]); 1130return kleidiai_weight_slot_ptr (header ,slot_index ); 1131 } 1132return nullptr ; 1133 } 1134if (slot_index == 0 ) { 1135size_out = ggml_nbytes (src0 ); 1136return static_cast < const uint8_t *> (src0 -> data ); 1137 } 1138return nullptr ; 1139 }; 1140 1141struct runtime_slot { 1142int slot_index ; 1143ggml_kleidiai_kernels * kernels ; 1144kernel_info * kernel ; 1145lhs_packing_info * lhs_info ; 1146size_t mr ; 1147size_t nr ; 1148size_t kr ; 1149size_t sr ; 1150size_t n_step ; 1151size_t lhs_packed_size ; 1152size_t lhs_offset ; 1153size_t lhs_bl ; 1154size_t rhs_bl ; 1155size_t pack_bl ; 1156size_t lhs_packed_offset0 ; 1157int assigned_threads ; 1158int thread_begin ; 1159int thread_end ; 1160const uint8_t * rhs_base ; 1161 }; 1162 1163 std::array < runtime_slot ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > runtime {}; 1164int runtime_count = 0 ; 1165 1166for (int slot = 0 ;slot < slot_total && runtime_count < GGML_KLEIDIAI_MAX_KERNEL_SLOTS ;++ slot ) { 1167ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 1168kernel_info * kinfo = is_gemv ?& kernels -> gemv :& kernels -> gemm ; 1169lhs_packing_info * linfo = is_gemv ?& kernels -> gemv_lhs_info :& kernels -> gemm_lhs_info ; 1170if (!kinfo || !linfo || !linfo -> packed_size_ex || !linfo -> pack_func_ex || !linfo -> get_offset || 1171 !kinfo -> get_rhs_packed_offset_ex || !kinfo -> run_kernel_ex || !kinfo -> get_dst_offset ) { 1172continue ; 1173 } 1174 1175size_t rhs_size = 0 ; 1176const uint8_t * rhs_ptr = weight_for_slot (slot ,rhs_size ); 1177if (!rhs_ptr || rhs_size == 0 ) { 1178continue ; 1179 } 1180 1181const kleidiai_block_args block_args = kleidiai_get_block_args (kernels -> rhs_type ); 1182 1183runtime [runtime_count ]= { 1184slot , 1185kernels , 1186kinfo , 1187linfo , 1188kinfo -> get_mr (), 1189kinfo -> get_nr (), 1190kinfo -> get_kr (), 1191kinfo -> get_sr (), 1192kinfo -> get_n_step (), 11930 , 11940 , 1195block_args .lhs_bl , 1196block_args .rhs_bl , 1197block_args .pack_bl , 11980 , 11990 , 12000 , 12010 , 1202rhs_ptr 1203 }; 1204++ runtime_count ; 1205 } 1206 1207if (runtime_count == 0 ) { 1208GGML_LOG_WARN ("kleidiai: no runtime kernel slot available for supported op %s\n" ,dst -> name ); 1209return false; 1210 } 1211 1212const int nth_total = params -> nth > 0 ?params -> nth :1 ; 1213const int ith_total = params -> ith ; 1214 1215int sme_slot = -1 ; 1216int non_sme_slot = -1 ; 1217for (int i = 0 ;i < runtime_count ;++ i ) { 1218if (is_sme_family (runtime [i ].kernels -> required_cpu )) { 1219sme_slot = i ; 1220break ; 1221 } 1222 } 1223 1224for (int i = 0 ;i < runtime_count ;++ i ) { 1225if (!is_sme_family (runtime [i ].kernels -> required_cpu )) { 1226non_sme_slot = i ; 1227break ; 1228 } 1229 } 1230 1231const int sme_cap_limit = ctx .sme_thread_cap ; 1232const bool use_hybrid = sme_cap_limit > 0 && 1233runtime_count > 1 && 1234nth_total > sme_cap_limit ; 1235// Heuristic: disable hybrid for very small workloads where per-slot overhead dominates. 1236// If rows are small or average columns per thread are small, keep single-slot. 1237size_t min_cols_per_thread = 0 ; 1238if (runtime_count > 0 && nth_total > 0 ) { 1239min_cols_per_thread = (size_t ) std::max < int64_t > (1 , (int64_t )ne01 / (int64_t )nth_total ); 1240 } 1241const bool too_small_for_hybrid = (min_cols_per_thread < 2 )|| (ne11 < 128 ); 1242 1243const bool hybrid_enabled = use_hybrid && !too_small_for_hybrid ; 1244 1245if (!hybrid_enabled ) { 1246int chosen_slot = 0 ; 1247if (too_small_for_hybrid && sme_slot != -1 ) { 1248chosen_slot = nth_total > sme_cap_limit && non_sme_slot != -1 ?non_sme_slot :sme_slot ; 1249 }else if (runtime_count > 1 && ctx .sme_thread_cap > 0 && nth_total > ctx .sme_thread_cap ) { 1250chosen_slot = 1 ; 1251 } 1252if (chosen_slot != 0 && chosen_slot < runtime_count ) { 1253runtime [0 ]= runtime [chosen_slot ]; 1254runtime [0 ].assigned_threads = 0 ; 1255runtime [0 ].thread_begin = 0 ; 1256runtime [0 ].thread_end = 0 ; 1257 } 1258runtime_count = runtime_count > 0 ?1 :0 ; 1259 1260// Recompute SME slot based on the collapsed runtime[0] 1261sme_slot = -1 ; 1262if (runtime_count > 0 && 1263is_sme_family (runtime [0 ].kernels -> required_cpu )) { 1264sme_slot = 0 ; 1265 } 1266 } 1267 1268int sme_cap = kleidiai_sme_thread_cap (); 1269if (sme_cap < 0 ) { 1270sme_cap = nth_total ; 1271 } 1272sme_cap = std::min (sme_cap ,nth_total ); 1273 1274int threads_remaining = nth_total ; 1275if (sme_slot != -1 ) { 1276int sme_threads = std::min (std::max (sme_cap ,0 ),threads_remaining ); 1277runtime [sme_slot ].assigned_threads = sme_threads ; 1278threads_remaining -= sme_threads ; 1279 } 1280 1281int fallback_indices [GGML_KLEIDIAI_MAX_KERNEL_SLOTS ]; 1282int fallback_count = 0 ; 1283// The current hybrid chain is bounded to SME + one non-SME fallback slot. 1284GGML_ASSERT (GGML_KLEIDIAI_MAX_KERNEL_SLOTS == 2 ); 1285for (int i = 0 ;i < runtime_count ;++ i ) { 1286if (i == sme_slot ) { 1287continue ; 1288 } 1289fallback_indices [fallback_count ++ ]= i ; 1290 } 1291 1292for (int fi = 0 ;fi < fallback_count ;++ fi ) { 1293if (threads_remaining <=0 ) { 1294break ; 1295 } 1296const int slot_index = fallback_indices [fi ]; 1297const int slots_left = fallback_count - fi ; 1298int share = (threads_remaining + slots_left - 1 ) /slots_left ; 1299share = std::min (share ,threads_remaining ); 1300runtime [slot_index ].assigned_threads = share ; 1301threads_remaining -= share ; 1302 } 1303 1304if (threads_remaining > 0 ) { 1305const int fallback_slot = (sme_slot != -1 ) ?sme_slot :0 ; 1306runtime [fallback_slot ].assigned_threads += threads_remaining ; 1307threads_remaining = 0 ; 1308 } 1309 1310int thread_cursor = 0 ; 1311for (int i = 0 ;i < runtime_count ;++ i ) { 1312runtime [i ].thread_begin = thread_cursor ; 1313thread_cursor += runtime [i ].assigned_threads ; 1314runtime [i ].thread_end = thread_cursor ; 1315 } 1316 1317if (thread_cursor < nth_total && runtime_count > 0 ) { 1318runtime [runtime_count - 1 ].assigned_threads += nth_total - thread_cursor ; 1319runtime [runtime_count - 1 ].thread_end = nth_total ; 1320 } 1321 1322int local_slot = -1 ; 1323int local_ith = 0 ; 1324for (int i = 0 ;i < runtime_count ;++ i ) { 1325if (ith_total >=runtime [i ].thread_begin && ith_total < runtime [i ].thread_end ) { 1326local_slot = i ; 1327local_ith = ith_total - runtime [i ].thread_begin ; 1328break ; 1329 } 1330 } 1331if (local_slot == -1 ) { 1332return false; 1333 } 1334 1335const size_t k = ne00 ; 1336const size_t m = ne11 ; 1337const size_t n = ne01 ; 1338 1339size_t cursor = 0 ; 1340for (int i = 0 ;i < runtime_count ;++ i ) { 1341runtime [i ].lhs_packed_size = runtime [i ].lhs_info -> packed_size_ex (m ,k ,runtime [i ].pack_bl ,runtime [i ].mr ,runtime [i ].kr ,runtime [i ].sr ); 1342cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 1343runtime [i ].lhs_offset = cursor ; 1344runtime [i ].lhs_packed_offset0 = runtime [i ].lhs_info -> get_packed_offset_ex (0 ,k ,runtime [i ].lhs_bl ,runtime [i ].mr ,runtime [i ].kr ,runtime [i ].sr ); 1345cursor += runtime [i ].lhs_packed_size ; 1346 } 1347 1348GGML_ASSERT (cursor <=params -> wsize ); 1349uint8_t * scratch = static_cast < uint8_t *> (params -> wdata ); 1350 1351size_t common_step = 1 ; 1352for (int i = 0 ;i < runtime_count ;++ i ) { 1353if (runtime [i ].assigned_threads == 0 ) { 1354continue ; 1355 } 1356size_t next_step = 0 ; 1357if (!lcm_size (common_step ,runtime [i ].n_step ?runtime [i ].n_step :1 ,next_step )) { 1358return false; 1359 } 1360common_step = next_step ; 1361 } 1362GGML_ASSERT (common_step > 0 ); 1363 1364const bool disable_chunking = ggml_is_numa (); 1365const size_t chunk_multiplier = std::max (1 ,ctx .chunk_multiplier ); 1366const size_t chunk_divisor = (nth_total == 1 || disable_chunking ) ? (size_t )nth_total : (size_t )nth_total * chunk_multiplier ; 1367size_t chunk_cols = align_up (std::max < size_t > (1 ,ceil_div_size (n ,chunk_divisor )),common_step ); 1368if (chunk_cols == 0 ) { 1369chunk_cols = common_step ; 1370 } 1371// If common_step is larger than n, the loop below runs one valid tail chunk 1372// with cols == n. 1373const size_t nchunk_size = std::max < size_t > (1 ,ceil_div_size (n ,chunk_cols )); 1374GGML_ASSERT (nchunk_size <= (size_t )INT_MAX ); 1375const int nchunk = (int )nchunk_size ; 1376const size_t dst_stride = dst -> nb [1 ]; 1377 1378auto run_chunk = [& ](runtime_slot & slot ,size_t global_start ,size_t cols ,uint8_t * dst_batch_base ) { 1379const size_t rhs_packed_offset = slot .kernel -> get_rhs_packed_offset_ex (global_start ,k ,slot .rhs_bl ); 1380const size_t dst_offset = slot .kernel -> get_dst_offset (0 ,global_start ,dst_stride ); 1381 1382const uint8_t * lhs_ptr = scratch + slot .lhs_offset + slot .lhs_packed_offset0 ; 1383const uint8_t * rhs_ptr = slot .rhs_base + rhs_packed_offset ; 1384float * dst_ptr = reinterpret_cast < float *> (dst_batch_base + dst_offset ); 1385 1386slot .kernel -> run_kernel_ex (m ,cols ,k ,slot .rhs_bl , 1387lhs_ptr , 1388rhs_ptr , 1389dst_ptr , 1390dst_stride , 1391sizeof (float ), 1392- FLT_MAX , 1393FLT_MAX ); 1394 }; 1395 1396for (int64_t batch_idx = 0 ;batch_idx < ne12 ;++ batch_idx ) { 1397const uint8_t * lhs_batch_base = static_cast < const uint8_t *> (src1 -> data )+ batch_idx * src1 -> nb [2 ]; 1398uint8_t * dst_batch_base = static_cast < uint8_t *> (dst -> data )+ batch_idx * dst -> nb [2 ]; 1399 1400if (runtime [local_slot ].assigned_threads > 0 ) { 1401runtime_slot & slot = runtime [local_slot ]; 1402const int64_t m_roundup_mr = kai_roundup ((int64_t )m , (int64_t )slot .mr ); 1403int64_t max_threads = slot .mr ? (m_roundup_mr / (int64_t )slot .mr ) :slot .assigned_threads ; 1404max_threads = std::max < int64_t > (1 ,max_threads ); 1405const int64_t use_threads = std::min < int64_t > (slot .assigned_threads ,max_threads ); 1406 1407if (local_ith < use_threads ) { 1408const int64_t num_m_per_thread0 = round_down ((size_t )(m_roundup_mr /use_threads ),slot .mr ); 1409const int64_t num_m_per_threadN_1 = (int64_t )m - (use_threads - 1 )* num_m_per_thread0 ; 1410 1411const int64_t m_start = (int64_t )local_ith * num_m_per_thread0 ; 1412const int64_t m_count = (local_ith == use_threads - 1 ) ?num_m_per_threadN_1 :num_m_per_thread0 ; 1413 1414const size_t base_packed_off = slot .lhs_info -> get_packed_offset_ex (m_start ,k ,slot .lhs_bl ,slot .mr ,slot .kr ,slot .sr ); 1415const size_t next_block_off = slot .lhs_info -> get_packed_offset_ex (m_start + slot .mr ,k ,slot .lhs_bl ,slot .mr ,slot .kr ,slot .sr ); 1416const size_t row_stride_bytes = slot .mr ? (next_block_off - base_packed_off ) /slot .mr :0 ; 1417 1418int64_t remaining = m_count ; 1419int64_t cur = m_start ; 1420 1421uint8_t * lhs_packed = scratch + slot .lhs_offset ; 1422while (remaining > 0 ) { 1423const int64_t row_in_group = cur ; 1424const int64_t avail = (int64_t )m - row_in_group ; 1425const int64_t take = std::min (avail ,remaining ); 1426 1427const size_t src_off = slot .lhs_info -> get_offset (row_in_group ,src1 -> nb [1 ]); 1428const void * src_ptr = lhs_batch_base + src_off ; 1429const size_t dst_off = base_packed_off + (size_t )(cur - m_start )* row_stride_bytes ; 1430void * dst_ptr = lhs_packed + dst_off ; 1431 1432slot .lhs_info -> pack_func_ex (take ,k ,slot .lhs_bl ,slot .mr ,slot .kr ,slot .sr ,0 ,src_ptr ,src1 -> nb [1 ],dst_ptr ); 1433 1434cur += take ; 1435remaining -= take ; 1436 } 1437 } 1438 } 1439 1440if (ith_total == 0 ) { 1441ggml_threadpool_chunk_set (params -> threadpool ,nth_total ); 1442 } 1443 1444// Publishes both LHS packing and the initialized dynamic chunk queue. 1445ggml_barrier (params -> threadpool ); 1446 1447runtime_slot & slot = runtime [local_slot ]; 1448int current_chunk = ith_total ; 1449while (current_chunk < nchunk ) { 1450const size_t global_start = (size_t )current_chunk * chunk_cols ; 1451if (global_start >=n ) { 1452break ; 1453 } 1454 1455const size_t cols = std::min (chunk_cols ,n - global_start ); 1456if (cols > 0 ) { 1457// KleidiAI GEMM/GEMV kernels accept arbitrary final tail widths; 1458// only non-tail chunks are guaranteed to be n_step-aligned. 1459run_chunk (slot ,global_start ,cols ,dst_batch_base ); 1460 } 1461 1462current_chunk = ggml_threadpool_chunk_add (params -> threadpool ,1 ); 1463 } 1464 1465if (batch_idx != ne12 - 1 ) { 1466ggml_barrier (params -> threadpool ); 1467 } 1468 } 1469 1470return true; 1471 } 1472 1473bool compute_forward_get_rows (struct ggml_compute_params * params ,struct ggml_tensor * dst ) { 1474GGML_ASSERT (dst -> src [0 ]-> type == GGML_TYPE_Q4_0 || dst -> src [0 ]-> type == GGML_TYPE_Q8_0 ); 1475const ggml_tensor * src0 = dst -> src [0 ]; 1476const ggml_tensor * src1 = dst -> src [1 ]; 1477 1478GGML_TENSOR_BINARY_OP_LOCALS 1479 1480const kleidiai_weight_header* header = kleidiai_weight_header_from_ptr (src0 -> data ); 1481const bool has_header = kleidiai_is_weight_header_valid (header ); 1482 1483 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 1484const bool want_q8 = src0 -> type == GGML_TYPE_Q8_0 ; 1485const int chain_count = want_q8 ?kleidiai_collect_q8_chain (kernel_chain ) 1486 :kleidiai_collect_q4_chain (kernel_chain ); 1487 1488ggml_kleidiai_kernels * kernels = nullptr ; 1489const uint8_t * packed_base = static_cast < const uint8_t *> (src0 -> data ); 1490 1491if (has_header && chain_count > 0 ) { 1492int select_slot = 0 ; 1493if (select_slot >=header -> slot_count ) { 1494select_slot = header -> slot_count - 1 ; 1495 } 1496if (select_slot >=0 && select_slot < chain_count ) { 1497kernels = kernel_chain [select_slot ]; 1498const uint8_t * slot_ptr = kleidiai_weight_slot_ptr (header ,select_slot ); 1499if (slot_ptr ) { 1500packed_base = slot_ptr ; 1501 } 1502 } 1503 } 1504 1505if (!kernels && chain_count > 0 ) { 1506kernels = kernel_chain [0 ]; 1507if (has_header ) { 1508const uint8_t * slot_ptr = kleidiai_weight_slot_ptr (header ,0 ); 1509if (slot_ptr ) { 1510packed_base = slot_ptr ; 1511 } 1512 } 1513 } 1514 1515if (!kernels ) { 1516return false; 1517 } 1518 1519rhs_packing_info * rhs_info = & kernels -> rhs_info ; 1520kernel_info * kernel = & kernels -> gemm ; 1521if (!rhs_info -> to_float || !kernel -> get_nr ) { 1522return false; 1523 } 1524 1525const int64_t nc = ne00 ; 1526const int64_t nr = ggml_nelements (src1 ); 1527 1528const ggml_type rhs_type = kernels -> rhs_type ; 1529size_t block_len = 0 ; 1530size_t num_bytes_multiplier = 0 ; 1531if (rhs_type == GGML_TYPE_Q4_0 ) { 1532block_len = QK4_0 ; 1533num_bytes_multiplier = sizeof (uint16_t ); 1534 }else if (rhs_type == GGML_TYPE_Q8_0 ) { 1535block_len = QK8_0 ; 1536num_bytes_multiplier = sizeof (float ); 1537 }else { 1538return false; 1539 } 1540 1541const size_t block_rows = kernel -> get_nr (); 1542const size_t kr = kernel -> get_kr (); 1543 1544const size_t packed_stride = rhs_info -> packed_stride (nc ,block_rows ,kr ,block_len ); 1545 1546const int ith = params -> ith ; 1547const int nth = params -> nth ; 1548 1549const int dr = (nr + nth - 1 ) /nth ; 1550const int ir0 = dr * ith ; 1551const int ir1 = MIN (ir0 + dr ,nr ); 1552 1553for (int64_t i = ir0 ;i < ir1 ;++ i ) { 1554GGML_ASSERT (src1 -> type == GGML_TYPE_I32 ); 1555int64_t row_idx = ((const int32_t * )src1 -> data )[i ]; 1556GGML_ASSERT (row_idx >=0 && row_idx < src0 -> ne [1 ]); 1557 1558float * out = (float * )((char * )dst -> data + i * nb1 ); 1559rhs_info -> to_float (packed_base ,row_idx ,nc ,out ,block_rows ,packed_stride ,kr ,block_len ,num_bytes_multiplier ); 1560 } 1561 1562return true; 1563 } 1564 1565public : 1566int repack (struct ggml_tensor * tensor ,const void * data ,size_t data_size ) { 1567GGML_ASSERT (tensor -> type == GGML_TYPE_Q4_0 || tensor -> type == GGML_TYPE_Q8_0 || tensor -> type == GGML_TYPE_F32 ); 1568const size_t n = tensor -> ne [1 ]; 1569const size_t k = tensor -> ne [0 ]; 1570 1571kleidiai_weight_header * header = kleidiai_weight_header_from_ptr (tensor -> data ); 1572if (!header ) { 1573return -1 ; 1574 } 1575 1576header -> magic = GGML_KLEIDIAI_PACK_MAGIC ; 1577header -> version = GGML_KLEIDIAI_PACK_VERSION ; 1578header -> slot_count = 0 ; 1579 1580uint8_t * base_ptr = static_cast < uint8_t *> (tensor -> data ); 1581size_t cursor = sizeof (kleidiai_weight_header ); 1582cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 1583 1584 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 1585const bool want_q8 = tensor -> type == GGML_TYPE_Q8_0 ; 1586const bool want_f32 = tensor -> type == GGML_TYPE_F32 ; 1587const int slot_total = want_f32 ?kleidiai_collect_f32_chain (kernel_chain ) 1588 :want_q8 ?kleidiai_collect_q8_chain (kernel_chain ) 1589 :kleidiai_collect_q4_chain (kernel_chain ); 1590const bool allow_fallback = kleidiai_pack_fallback_allowed (); 1591 1592 std::vector < int8_t > qdata ; 1593 std::vector < float > scales ; 1594 std::vector < float > bias ; 1595 1596if (want_q8 && slot_total > 0 ) { 1597qdata .resize (n * k ,0 ); 1598scales .resize (n ,0.0f ); 1599 1600const size_t row_stride = tensor -> nb [1 ]; 1601const size_t k_blocks = (k + QK8_0 - 1 ) /QK8_0 ; 1602 1603for (size_t row = 0 ;row < n ;++ row ) { 1604const auto * row_blocks = reinterpret_cast < const block_q8_0 *> ( 1605static_cast < const uint8_t *> (data )+ row * row_stride ); 1606 1607float max_abs = 0.0f ; 1608for (size_t block = 0 ;block < k_blocks ;++ block ) { 1609const block_q8_0 & blk = row_blocks [block ]; 1610const float d = GGML_FP16_TO_FP32 (blk .d ); 1611for (size_t l = 0 ;l < QK8_0 ;++ l ) { 1612const size_t linear_idx = block * QK8_0 + l ; 1613if (linear_idx >=k ) { 1614break ; 1615 } 1616const float value = d * static_cast < float > (blk .qs [l ]); 1617max_abs = std::max (max_abs , std::fabs (value )); 1618 } 1619 } 1620 1621float scale = max_abs > 0.0f ?max_abs /127.0f :0.0f ; 1622scales [row ]= scale ; 1623const float inv_scale = scale > 0.0f ?1.0f /scale :0.0f ; 1624 1625for (size_t block = 0 ;block < k_blocks ;++ block ) { 1626const block_q8_0 & blk = row_blocks [block ]; 1627const float d = GGML_FP16_TO_FP32 (blk .d ); 1628for (size_t l = 0 ;l < QK8_0 ;++ l ) { 1629const size_t linear_idx = block * QK8_0 + l ; 1630if (linear_idx >=k ) { 1631break ; 1632 } 1633const float value = d * static_cast < float > (blk .qs [l ]); 1634int32_t q = scale > 0.0f ?static_cast < int32_t > (std::lround (value * inv_scale )) :0 ; 1635q = std::clamp (q ,-127 ,127 ); 1636qdata [row * k + linear_idx ]= static_cast < int8_t > (q ); 1637 } 1638 } 1639 } 1640 } 1641 1642if (want_f32 && slot_total > 0 ) { 1643bias .resize (n ,0.0f ); 1644 } 1645 1646for (int slot = 0 ;slot < slot_total && slot < GGML_KLEIDIAI_MAX_KERNEL_SLOTS ;++ slot ) { 1647if (!allow_fallback && slot > 0 ) { 1648break ; 1649 } 1650ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 1651kernel_info * kernel = & kernels -> gemm ; 1652rhs_packing_info * rhs_info = & kernels -> rhs_info ; 1653if (!rhs_info || !rhs_info -> pack_func_ex || !rhs_info -> packed_size_ex || !kernel ) { 1654continue ; 1655 } 1656 1657const size_t nr = kernel -> get_nr (); 1658const size_t kr = kernel -> get_kr (); 1659const size_t sr = kernel -> get_sr (); 1660const ggml_type rhs_type = kernels -> rhs_type ; 1661const size_t block_len = rhs_type == GGML_TYPE_Q8_0 ?QK8_0 : 1662rhs_type == GGML_TYPE_Q4_0 ?QK4_0 : 1663rhs_type == GGML_TYPE_F32 ?0 :SIZE_MAX ; 1664if (block_len == SIZE_MAX ) { 1665continue ; 1666 } 1667 1668const size_t packed_size = rhs_info -> packed_size_ex (n ,k ,nr ,kr ,block_len ); 1669const size_t aligned_cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 1670 1671uint8_t * dst_ptr = base_ptr + aligned_cursor ; 1672 1673if (rhs_type == GGML_TYPE_Q4_0 ) { 1674struct kai_rhs_pack_qs4cxs1s0_param params ; 1675params .lhs_zero_point = 1 ; 1676params .rhs_zero_point = 8 ; 1677rhs_info -> pack_func_ex (1 ,n ,k ,nr ,kr ,sr ,QK4_0 ,0 , 1678static_cast < const uint8_t *> (data ),nullptr ,nullptr , 1679dst_ptr ,0 ,& params ); 1680 }else if (rhs_type == GGML_TYPE_Q8_0 ) { 1681struct kai_rhs_pack_qsi8cx_params params ; 1682params .lhs_zero_point = 1 ; 1683params .scale_multiplier = 1.0f ; 1684rhs_info -> pack_func_ex (1 ,n ,k ,nr ,kr ,sr ,0 ,0 , 1685qdata .data (),nullptr ,scales .data (), 1686dst_ptr ,0 ,& params ); 1687 }else if (rhs_type == GGML_TYPE_F32 ) { 1688rhs_info -> pack_func_ex (1 ,n ,k ,nr ,kr ,sr ,0 ,tensor -> nb [1 ], 1689data ,bias .data (),nullptr , 1690dst_ptr ,0 ,nullptr ); 1691 }else { 1692continue ; 1693 } 1694 1695header -> offsets [header -> slot_count ]= aligned_cursor ; 1696header -> sizes [header -> slot_count ]= packed_size ; 1697++ header -> slot_count ; 1698 1699cursor = aligned_cursor + packed_size ; 1700 } 1701 1702if (header -> slot_count == 0 ) { 1703header -> magic = 0 ; 1704header -> version = 0 ; 1705memcpy (tensor -> data ,data ,data_size ); 1706 } 1707 1708return 0 ; 1709 } 1710}; 1711 1712static ggml::cpu::tensor_traits * get_tensor_traits (ggml_backend_buffer_t ,struct ggml_tensor * ) { 1713static tensor_traits traits ; 1714return & traits ; 1715} 1716}// namespace ggml::cpu::kleidiai 1717 1718static enum ggml_status ggml_backend_cpu_kleidiai_buffer_init_tensor (ggml_backend_buffer_t buffer ,struct ggml_tensor * tensor ) { 1719tensor -> extra = (void * ) ggml::cpu::kleidiai::get_tensor_traits (buffer ,tensor ); 1720 1721return GGML_STATUS_SUCCESS ; 1722GGML_UNUSED (buffer ); 1723} 1724 1725static void ggml_backend_cpu_kleidiai_buffer_set_tensor (ggml_backend_buffer_t buffer ,struct ggml_tensor * tensor , 1726const void * data ,size_t offset ,size_t size ) { 1727GGML_ASSERT (offset == 0 ); 1728GGML_ASSERT (size == ggml_nbytes (tensor )); 1729 1730auto tensor_traits = (ggml::cpu::kleidiai::tensor_traits * )tensor -> extra ; 1731auto OK = tensor_traits -> repack (tensor ,data ,size ); 1732 1733GGML_ASSERT (OK == 0 ); 1734GGML_UNUSED (buffer ); 1735} 1736 1737static const char * ggml_backend_cpu_kleidiai_buffer_type_get_name (ggml_backend_buffer_type_t buft ) { 1738GGML_UNUSED (buft ); 1739return "CPU_KLEIDIAI" ; 1740} 1741 1742static ggml_backend_buffer_t ggml_backend_cpu_kleidiai_buffer_type_alloc_buffer (ggml_backend_buffer_type_t buft ,size_t size ) { 1743ggml_backend_buffer_t buffer = ggml_backend_buft_alloc_buffer (ggml_backend_cpu_buffer_type (),size ); 1744 1745if (buffer == nullptr ) { 1746return nullptr ; 1747 } 1748 1749buffer -> buft = buft ; 1750buffer -> iface .init_tensor = ggml_backend_cpu_kleidiai_buffer_init_tensor ; 1751buffer -> iface .set_tensor = ggml_backend_cpu_kleidiai_buffer_set_tensor ; 1752buffer -> iface .get_tensor = nullptr ; 1753buffer -> iface .cpy_tensor = nullptr ; 1754return buffer ; 1755} 1756 1757static size_t ggml_backend_cpu_kleidiai_buffer_type_get_alignment (ggml_backend_buffer_type_t buft ) { 1758GGML_UNUSED (buft ); 1759return TENSOR_ALIGNMENT ; 1760} 1761 1762static size_t ggml_backend_cpu_kleidiai_buffer_type_get_alloc_size (ggml_backend_buffer_type_t buft ,const struct ggml_tensor * tensor ) { 1763GGML_UNUSED (buft ); 1764 1765if (tensor -> type != GGML_TYPE_Q4_0 && tensor -> type != GGML_TYPE_Q8_0 && tensor -> type != GGML_TYPE_F32 ) { 1766return ggml_nbytes (tensor ); 1767 } 1768 1769const size_t n = tensor -> ne [1 ]; 1770const size_t k = tensor -> ne [0 ]; 1771 1772size_t cursor = sizeof (kleidiai_weight_header ); 1773cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 1774 1775 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 1776const bool want_q8 = tensor -> type == GGML_TYPE_Q8_0 ; 1777const bool want_f32 = tensor -> type == GGML_TYPE_F32 ; 1778const int slot_total = want_f32 ?kleidiai_collect_f32_chain (kernel_chain ) 1779 :want_q8 ?kleidiai_collect_q8_chain (kernel_chain ) 1780 :kleidiai_collect_q4_chain (kernel_chain ); 1781const bool allow_fallback = kleidiai_pack_fallback_allowed (); 1782 1783size_t slot_count = 0 ; 1784for (int slot = 0 ;slot < slot_total ;++ slot ) { 1785if (!allow_fallback && slot > 0 ) { 1786break ; 1787 } 1788ggml_kleidiai_kernels * kernels = kernel_chain [slot ]; 1789if (!kernels ) { 1790continue ; 1791 } 1792kernel_info * kernel = & kernels -> gemm ; 1793rhs_packing_info * rhs_info = & kernels -> rhs_info ; 1794if (!kernel || !rhs_info || !rhs_info -> packed_size_ex ) { 1795continue ; 1796 } 1797 1798const ggml_type rhs_type = kernels -> rhs_type ; 1799const size_t block_len = rhs_type == GGML_TYPE_Q4_0 ?QK4_0 : 1800rhs_type == GGML_TYPE_Q8_0 ?QK8_0 : 1801rhs_type == GGML_TYPE_F32 ?0 :SIZE_MAX ; 1802if (block_len == SIZE_MAX ) { 1803continue ; 1804 } 1805 1806cursor = align_up (cursor ,GGML_KLEIDIAI_PACK_ALIGN ); 1807cursor += rhs_info -> packed_size_ex (n ,k ,kernel -> get_nr (),kernel -> get_kr (),block_len ); 1808++ slot_count ; 1809 } 1810 1811if (slot_count == 0 ) { 1812return ggml_nbytes (tensor ); 1813 } 1814 1815return std::max (cursor ,ggml_nbytes (tensor )); 1816} 1817 1818namespace ggml::cpu::kleidiai { 1819class extra_buffer_type : ggml::cpu::extra_buffer_type { 1820bool supports_op (ggml_backend_dev_t ,const struct ggml_tensor * op )override { 1821 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 1822const int slot_total = kleidiai_collect_kernel_chain (op ,kernel_chain ); 1823const bool src0_is_kleidiai = 1824op -> src [0 ]-> buffer && 1825 (ggml_n_dims (op -> src [0 ])== 2 )&& 1826op -> src [0 ]-> buffer -> buft -> context == this && 1827slot_total > 0 ; 1828 1829if ((op -> op == GGML_OP_MUL_MAT || op -> op == GGML_OP_GET_ROWS )&& 1830 (op -> src [0 ]-> type == GGML_TYPE_Q4_0 || op -> src [0 ]-> type == GGML_TYPE_Q8_0 || op -> src [0 ]-> type == GGML_TYPE_F32 )&& 1831src0_is_kleidiai ) { 1832if (op -> src [0 ]-> type == GGML_TYPE_Q4_0 && ctx .kernels_q4 == nullptr ) { 1833return false; 1834 } 1835if (op -> src [0 ]-> type == GGML_TYPE_Q8_0 && ctx .kernels_q8 == nullptr ) { 1836return false; 1837 } 1838if (op -> src [0 ]-> type == GGML_TYPE_F32 && ctx .kernels_f32 == nullptr ) { 1839return false; 1840 } 1841if (op -> src [1 ]-> buffer && !ggml_backend_buft_is_host (op -> src [1 ]-> buffer -> buft )) { 1842return false; 1843 } 1844 1845if (op -> src [0 ]-> type == GGML_TYPE_Q4_0 || op -> src [0 ]-> type == GGML_TYPE_Q8_0 ) { 1846if ((op -> src [1 ]-> type == GGML_TYPE_F32 || op -> src [1 ]-> type == GGML_TYPE_I32 )&& 1847ggml_ne (op -> src [1 ],3 )== 1 ) { 1848return true; 1849 } 1850return false; 1851 } 1852 1853if (op -> op != GGML_OP_MUL_MAT || op -> src [1 ]-> type != GGML_TYPE_F32 || op -> type != GGML_TYPE_F32 ) { 1854return false; 1855 } 1856 1857return true; 1858 } 1859 1860return false; 1861 } 1862 1863 ggml::cpu::tensor_traits * get_tensor_traits (const struct ggml_tensor * op )override { 1864if (op -> op == GGML_OP_MUL_MAT || op -> op == GGML_OP_GET_ROWS ) { 1865if (op -> src [0 ]-> buffer && op -> src [0 ]-> buffer -> buft -> context == this ) { 1866return (ggml::cpu::tensor_traits * )op -> src [0 ]-> extra ; 1867 }else { 1868// KleidiAI only has kernels for Q4_0 and Q8_0. For a quantized weight of any 1869// other type (K-quants, IQ) it declines the op and returns nullptr below, so 1870// KleidiAI does not accelerate it. Another CPU backend may still take the op, 1871// and this can run during graph planning, so the message says what KleidiAI 1872// did rather than what ends up executing. Warn once per process. 1873if (ggml_is_quantized (op -> src [0 ]-> type )&& 1874op -> src [0 ]-> type != GGML_TYPE_Q4_0 && op -> src [0 ]-> type != GGML_TYPE_Q8_0 ) { 1875static std::atomic < bool > warned (false); 1876if (!warned .exchange (true)) { 1877GGML_LOG_WARN ("kleidiai: no kernel for tensor type %s, not accelerated by KleidiAI " 1878"(kernels available for Q4_0 and Q8_0)\n" , 1879ggml_type_name (op -> src [0 ]-> type )); 1880 } 1881 } 1882if (op -> src [0 ]-> type != GGML_TYPE_F16 ) { 1883return nullptr ; 1884 } 1885 std::array < ggml_kleidiai_kernels * ,GGML_KLEIDIAI_MAX_KERNEL_SLOTS > kernel_chain ; 1886const int slot_total = kleidiai_collect_kernel_chain (op ,kernel_chain ); 1887if (slot_total > 0 && op -> src [1 ]-> ne [1 ]> 1 ) { 1888if ((op -> src [0 ]-> nb [1 ]* op -> src [0 ]-> ne [1 ]!= op -> src [0 ]-> nb [2 ])|| 1889 (op -> src [1 ]-> nb [1 ]* op -> src [1 ]-> ne [1 ]!= op -> src [1 ]-> nb [2 ])) { 1890return nullptr ; 1891 } 1892return ggml::cpu::kleidiai::get_tensor_traits (NULL ,NULL ); 1893 } 1894 } 1895 } 1896return nullptr ; 1897 } 1898}; 1899}// namespace ggml::cpu::kleidiai 1900 1901ggml_backend_buffer_type_t ggml_backend_cpu_kleidiai_buffer_type (void ) { 1902static ggml::cpu::kleidiai::extra_buffer_type ctx ; 1903static struct ggml_backend_buffer_type ggml_backend_cpu_buffer_type_kleidiai = { 1904/* .iface = */ { 1905/* .get_name = */ ggml_backend_cpu_kleidiai_buffer_type_get_name , 1906/* .alloc_buffer = */ ggml_backend_cpu_kleidiai_buffer_type_alloc_buffer , 1907/* .get_alignment = */ ggml_backend_cpu_kleidiai_buffer_type_get_alignment , 1908/* .get_max_size = */ nullptr ,// defaults to SIZE_MAX 1909/* .get_alloc_size = */ ggml_backend_cpu_kleidiai_buffer_type_get_alloc_size , 1910/* .is_host = */ nullptr , 1911 }, 1912/* .device = */ ggml_backend_reg_dev_get (ggml_backend_cpu_reg (),0 ), 1913/* .context = */ & ctx , 1914 }; 1915 1916init_kleidiai_context (); 1917 1918return & ggml_backend_cpu_buffer_type_kleidiai ; 1919}