char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aman Chadha(IVIXMMI)ggml: avoid KleidiAI buffer type init on dispatch (#27891)b81c99b47

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