char-fork/llama.cpp

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

Max Krasnyanskyhexagon: support for multi-device model split (aka row-split) (#28589)eafe15a5e

char/aliceai-t5-moe
265.9 KiB7079 linesraw
1#include <assert.h>
2#include <inttypes.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <time.h>
7
8#include <atomic>
9#include <memory>
10#include <chrono>
11#include <mutex>
12#include <thread>
13#include <cstddef>
14#include <stdexcept>
15#include <string>
16#include <sstream>
17#include <iomanip>
18#include <unordered_set>
19#include <unordered_map>
20#include <regex>
21#include <queue>
22#include <deque>
23#include <algorithm>
24
25#ifdef _WIN32
26#    define WIN32_LEAN_AND_MEAN
27#    ifndef NOMINMAX
28#       define NOMINMAX
29#    endif
30#    include <windows.h>
31#    include <sal.h>
32#else
33#    include <semaphore.h>
34#    include <unistd.h>
35#endif
36
37#pragma clang diagnostic ignored "-Wnested-anon-types"
38#pragma clang diagnostic ignored "-Wlanguage-extension-token"
39#pragma clang diagnostic ignored "-Wgnu-anonymous-struct"
40#pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
41
42#include <AEEStdErr.h>
43#include <dspqueue.h>
44#include <rpcmem.h>
45
46#define GGML_COMMON_IMPL_CPP
47#include "ggml-backend-impl.h"
48#include "ggml-common.h"
49#include "ggml-hexagon.h"
50#include "ggml-impl.h"
51#include "ggml-quants.h"
52#include "htp-opnode.h"
53#include "htp-ops.h"
54#include "htp/matmul-ops.h"
55#include "htp/flash-attn-ops.h"
56#include "htp/unary-ops.h"
57#include "htp/get-rows-ops.h"
58#include "htp/set-rows-ops.h"
59#include "htp/rope-ops.h"
60#include "htp_iface.h"
61#include "htp-drv.h"
62
63using intvec  = std::vector<int>;
64using uintvec = std::vector<unsigned int>;
65using u32vec  = std::vector<uint32_t>;
66
67#define GGML_HEXAGON_MAX_SESSIONS          16
68
69#define GGML_HEXAGON_FENCE_SLOT_SIZE       128
70
71struct ggml_hexagon_device_config {
72    int         physical_idx = 0;
73    int         virtual_idx  = 0;
74    int         domain_id    = 0;
75    std::string domain_name;
76    std::string name;
77
78    std::vector<ggml_hexagon_device_config> mdev_group;
79};
80
81static ggml_hexagon_device_config opt_device_configs[GGML_HEXAGON_MAX_SESSIONS];
82
83static int    opt_arch    = 0; // autodetect
84static size_t opt_ndev    = 1;
85static size_t opt_nhvx    = 0; // use all
86static int    opt_nhmx    = 1; // when set, enable HMX; when 0, use HVX only
87static size_t opt_vmem    = HTP_OP_MAX_VMEM_DEFAULT;  // max available va space for buffer mappings
88static size_t opt_mbuf    = 1ul * 1024 * 1024 * 1024; // max buffer size
89static int    opt_etm     = 0;
90static int    opt_verbose = 0;
91static int    opt_profile = 0; // profiling mode (0-disabled, 1-basic, 2-pmu)
92static bool   opt_hostbuf = false;
93
94static int    opt_mm_select = 3; // 3 = HMX -> Tiled -> Flat -> CPU, 2 = Tiled -> Flat -> CPU, 1 = Flat -> CPU
95static int    opt_fa_select = 2; // 2 = HMX -> HVX -> CPU, 1 = HVX -> CPU, 0 = CPU (unsupported)
96static int    opt_ar_select = 2; // 2 = fused ALLREDUCE+ADD (DMA, default), 1 = unfused ALLREDUCE (DMA), 0 = fallback to CPY+FENCE
97
98// Default PMU events, if profiling with PMU (mode=2) is enabled
99// See https://docs.qualcomm.com/doc/80-N2040-60/topic/pmu-events.html
100//     https://docs.qualcomm.com/doc/80-N2040-61/topic/hvx-pmu-events.html
101static u32vec opt_pmu_evt { 0x3, 0x111, 0x100, 0x105, 0x240, 0x256, 0x7D, 0x8C };
102
103static int opt_opbatch  = 1280; // max number of ops in a batch
104static int opt_opqueue  = 32;   // max number of pending batches
105static int opt_optrace  = 0;    // trace buffer size per thread (0 means default)
106static int opt_oppoll   = 0;    // polling for batch completions
107static int opt_opfusion = 1;    // enable/disable op fusion
108
109enum ggml_hexagon_fusion_flags {
110    GGML_HEXAGON_FUSE_ALLREDUCE_ADD = (1 << 1), // 2
111    GGML_HEXAGON_FUSE_RMS_NORM_MUL  = (1 << 2), // 4
112    GGML_HEXAGON_FUSE_MUL_MAT_ADD   = (1 << 3), // 8
113    GGML_HEXAGON_FUSE_MUL_MAT_NX    = (1 << 4), // 16
114    GGML_HEXAGON_FUSE_MUL_MAT_ID_NX = (1 << 5), // 32
115};
116
117static inline bool ggml_hexagon_is_fusion_enabled(int flag) {
118    if (opt_opfusion <= 0) return false;
119    if (opt_opfusion == 1) return true; // 1 enables all
120    return (opt_opfusion & flag) != 0;
121}
122
123static std::regex* opt_opfilter = NULL; // regex of ops to not claim
124
125#define HEX_VERBOSE(...) \
126    if (opt_verbose) GGML_LOG_DEBUG(__VA_ARGS__)
127
128static const char * status_to_str(uint32_t status) {
129    switch (status) {
130        case HTP_STATUS_OK:
131            return "OK";
132        case HTP_STATUS_NO_SUPPORT:
133            return "NO-SUPPORT";
134        case HTP_STATUS_INVAL_PARAMS:
135            return "INVAL-PARAMS";
136        case HTP_STATUS_VTCM_TOO_SMALL:
137            return "VTCM-TOO-SMALL";
138        case HTP_STATUS_INTERNAL_ERR:
139            return "INTERNAL-ERROR";
140        default:
141            return "UNKNOWN";
142    }
143}
144
145// ** debug helpers
146
147static void ggml_hexagon_dump_op_exec(const std::string &sess_name, const htp_opnode & node, const uint32_t req_flags) {
148    if (!opt_verbose) return;
149
150    htp_opformat fmt(node);
151    GGML_LOG_DEBUG("ggml-hex: %s execute-op %s|%s|%s|%s|%s|%s|%s|flags 0x%x\n", sess_name.c_str(),
152                node.op_name().c_str(), fmt.names, fmt.dims, fmt.types, fmt.strides, fmt.buffs, fmt.kparams, req_flags);
153}
154
155static void ggml_hexagon_dump_op_supp(const std::string &sess_name, const struct ggml_tensor * op, bool supp) {
156    if (!opt_verbose) return;
157
158    htp_opformat fmt(htp_opformat(htp_opnode(HTP_OP_INVALID, const_cast<ggml_tensor*>(op))));
159    GGML_LOG_DEBUG("ggml-hex: %s supports-op %s|%s|%s|%s|%s|%s|%s\n", sess_name.c_str(),
160                ggml_op_desc(op), fmt.names, fmt.dims, fmt.types, fmt.strides, fmt.buffs, supp ? "yes" : "no");
161}
162
163static const char * htp_event_name(uint16_t id) {
164    switch (id) {
165        case HTP_TRACE_EVT_DMA:            return "DMA";
166        case HTP_TRACE_EVT_HVX_COMP:       return "HVX_COMP";
167        case HTP_TRACE_EVT_HVX_A_QUANT:    return "HVX_A_QUANT";
168        case HTP_TRACE_EVT_HVX_A_PREP:     return "HVX_A_PREP";
169        case HTP_TRACE_EVT_HVX_W_DEQUANT:  return "HVX_W_DEQUANT";
170        case HTP_TRACE_EVT_HVX_W_PREP:     return "HVX_W_PREP";
171        case HTP_TRACE_EVT_HVX_O_PROC:     return "HVX_O_PROC";
172        case HTP_TRACE_EVT_HVX_FA_QK:      return "HVX_QK_FA";
173        case HTP_TRACE_EVT_HVX_FA_SFM:     return "HVX_SFM_FA";
174        case HTP_TRACE_EVT_HVX_FA_Q_PREP:  return "HVX_Q_PREP";
175        case HTP_TRACE_EVT_HVX_FA_K_PREP:  return "HVX_K_PREP";
176        case HTP_TRACE_EVT_HVX_FA_V_PREP:  return "HVX_V_PREP";
177        case HTP_TRACE_EVT_HMX_COMP:       return "HMX_COMP";
178        case HTP_TRACE_EVT_L2FLUSH:        return "L2FLUSH";
179        case HTP_TRACE_EVT_INIT:           return "INIT";
180        case HTP_TRACE_EVT_BUFF:           return "BUFF";
181        case HTP_TRACE_EVT_FENCE:          return "FENCE";
182        default:                           return "UNKNOWN";
183    }
184}
185
186static void ggml_hexagon_dump_op_prof(const std::string &sess_name, const htp_opnode & node, const htp_prof_desc & pd) {
187    if (!opt_profile) return;
188
189    uint32_t op_usec = pd.usecs;
190    uint32_t op_cycles = pd.cycles_stop - pd.cycles_start;
191    const uint32_t * pmu = pd.pmu;
192
193    char pmu_str[256] = "";
194    if (opt_profile == 2) {
195        static_assert(HTP_PROF_PMU_NCNT == 8, "current implementation assumes 8 PMU counters");
196        snprintf(pmu_str, sizeof(pmu_str), " pmu [%u,%u,%u,%u,%u,%u,%u,%u]",
197                pmu[0], pmu[1], pmu[2], pmu[3], pmu[4], pmu[5], pmu[6], pmu[7]);
198    }
199
200    htp_opformat fmt(node);
201    float mhz = op_usec > 0 ? (float) op_cycles / op_usec : 0.0f;
202    GGML_LOG_DEBUG("ggml-hex: %s profile-op %s|%s|%s|%s|%s|%s|usec %u cycles %u start %u mhz %.1f%s\n", sess_name.c_str(),
203            node.op_name().c_str(), fmt.names, fmt.dims, fmt.types, fmt.strides, fmt.kparams, op_usec, op_cycles, pd.cycles_start, mhz, pmu_str);
204}
205
206static void ggml_hexagon_dump_batch_prof(const std::string & sess_name, const htp_opbatch_rsp & rsp) {
207    uint64_t batch_cycles = rsp.cycles_stop - rsp.cycles_start;
208    float batch_mhz = rsp.usecs > 0 ? (float) batch_cycles / rsp.usecs : 0.0f;
209
210    char evt_str[256] = "----";
211    if (opt_profile == 3) {
212        snprintf(evt_str, sizeof(evt_str), "evt-cnt %u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u",
213                rsp.n_traces[0], rsp.n_traces[1], rsp.n_traces[2], rsp.n_traces[3],
214                rsp.n_traces[4], rsp.n_traces[5], rsp.n_traces[6], rsp.n_traces[7],
215                rsp.n_traces[8], rsp.n_traces[9], rsp.n_traces[10]);
216    }
217
218    GGML_LOG_DEBUG("ggml-hex: %s profile-op OPBATCH|----|n-ops %u|%s|----|----|usec %u cycles %llu start %llu mhz %.1f\n",
219                   sess_name.c_str(), rsp.n_ops, evt_str, rsp.usecs, (unsigned long long) batch_cycles, (unsigned long long) rsp.cycles_start, batch_mhz);
220}
221
222static void ggml_hexagon_dump_trace_events(const std::string & sess_name, const htp_opbatch_rsp & rsp,
223                                           const htp_trace_desc * trace_events, uint32_t n_traces) {
224    if (opt_profile == 3 && trace_events) {
225        uint32_t valid_cnt[HTP_MAX_NTHREADS + 1] = {0};
226        for (uint32_t t = 0; t <= HTP_MAX_NTHREADS; t++) {
227            uint32_t count = rsp.n_traces[t];
228            valid_cnt[t] = count > n_traces ? n_traces : count;
229        }
230
231        for (uint32_t t = 0; t <= HTP_MAX_NTHREADS; t++) {
232            for (uint32_t idx = 0; idx < valid_cnt[t]; idx++) {
233                const auto & e = trace_events[t * n_traces + idx];
234                bool is_stop = (e.info & 0x8000) != 0;
235                uint16_t info = e.info & 0x7FFF;
236                GGML_LOG_DEBUG("ggml-hex: %s trace-evt %s: thread %u info %u %s %u\n",
237                               sess_name.c_str(), htp_event_name(e.id), t, info, is_stop ? "stop" : "start", e.cycles);
238            }
239        }
240    }
241}
242
243enum ggml_hexagon_tensor_flags {
244    GGML_HEXAGON_TENSOR_REPACK    = (1 << 0),
245    GGML_HEXAGON_TENSOR_WEIGHT    = (1 << 1),
246    GGML_HEXAGON_TENSOR_FENCE     = (1 << 2),
247    GGML_HEXAGON_TENSOR_FUSEABLE  = (1 << 3),
248};
249
250static inline bool ggml_hexagon_is_repack_type(enum ggml_type type) {
251    return type == GGML_TYPE_Q4_0 || type == GGML_TYPE_Q4_1 ||
252           type == GGML_TYPE_Q8_0 || type == GGML_TYPE_IQ4_NL ||
253           type == GGML_TYPE_MXFP4;
254}
255
256static inline bool ggml_hexagon_is_hmx_weight_type(enum ggml_type type) {
257    return type == GGML_TYPE_F16 || type == GGML_TYPE_F32 || ggml_hexagon_is_repack_type(type);
258}
259
260struct ggml_hexagon_session;
261
262static void ggml_hexagon_precompute_matmul_params(
263    const struct ggml_hexagon_session * sess,
264    const struct ggml_tensor * src0,
265    const struct ggml_tensor * src1,
266    const struct ggml_tensor * dst,
267    struct htp_mm_kernel_params * kparams
268);
269
270static void ggml_hexagon_precompute_fused_matmul_add_params(
271    const struct ggml_hexagon_session * sess,
272    const struct ggml_tensor * src0,
273    const struct ggml_tensor * src1,
274    const struct ggml_tensor * src2,
275    const struct ggml_tensor * dst,
276    struct htp_mm_kernel_params * kparams
277);
278
279static void ggml_hexagon_precompute_unary_params(
280    const struct ggml_hexagon_session * sess,
281    uint32_t op,
282    const struct ggml_tensor * src0,
283    const struct ggml_tensor * src1,
284    const struct ggml_tensor * dst,
285    struct htp_unary_kernel_params * kparams
286);
287
288static void ggml_hexagon_precompute_get_rows_params(
289    const struct ggml_hexagon_session * sess,
290    const struct ggml_tensor * src0,
291    const struct ggml_tensor * src1,
292    const struct ggml_tensor * dst,
293    struct htp_get_rows_kernel_params * kparams
294);
295
296static void ggml_hexagon_precompute_set_rows_params(
297    const struct ggml_hexagon_session * sess,
298    const struct ggml_tensor * src0,
299    const struct ggml_tensor * src1,
300    const struct ggml_tensor * dst,
301    struct htp_set_rows_kernel_params * kparams
302);
303
304static void ggml_hexagon_precompute_rope_params(
305    const struct ggml_hexagon_session * sess,
306    const struct ggml_tensor * op,
307    struct htp_rope_kernel_params * kparams
308);
309
310static void ggml_hexagon_precompute_fused_mmnx_params(
311    const struct ggml_hexagon_session * sess,
312    const struct ggml_tensor * src0,
313    const struct ggml_tensor * src1,
314    int32_t n_weights,
315    struct htp_mm_kernel_params * kparams
316);
317
318static void ggml_hexagon_precompute_fused_mmidnx_params(
319    const struct ggml_hexagon_session * sess,
320    const struct ggml_tensor * src0,
321    const struct ggml_tensor * src1,
322    const struct ggml_tensor * dst,
323    int32_t n_weights,
324    struct htp_mm_kernel_params * kparams
325);
326
327static bool ggml_hexagon_precompute_allreduce_params(
328    const struct ggml_hexagon_session * sess,
329    const struct ggml_tensor * dst,
330    uint32_t rank,
331    uint32_t n_ranks,
332    bool has_add,
333    bool is_row_bcast,
334    struct htp_allreduce_kernel_params * kparams
335);
336
337static bool mm_is_hmx_eligible(const ggml_tensor * t);
338static bool is_supported_mul_mat_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams);
339static bool is_supported_mul_mat_id_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams);
340static bool is_mergeable_mul_mat(const ggml_tensor * t);
341static bool is_mergeable_mul_mat_pair(const ggml_tensor * n1, const ggml_tensor * n2);
342static bool is_mergeable_mul_mat_id(const ggml_tensor * t);
343static bool is_mergeable_mul_mat_id_pair(const ggml_tensor * n1, const ggml_tensor * n2);
344
345// ** backend sessions
346
347struct ggml_hexagon_tensor_extra {
348    std::vector<uint8_t> shadow_buf;
349    size_t               shadow_size { 0 };
350    uint32_t             flags { 0 };
351};
352
353static inline bool ggml_hexagon_tensor_is_fuseable(const struct ggml_tensor * t) {
354    if (!t->extra) return false;
355    auto extra = (const struct ggml_hexagon_tensor_extra *) t->extra;
356    return (extra->flags & GGML_HEXAGON_TENSOR_FUSEABLE) != 0;
357}
358
359static inline bool ggml_hexagon_tensors_overlap(const struct ggml_tensor * a, const struct ggml_tensor * b) {
360    const uintptr_t a0 = (uintptr_t) a->data;
361    const uintptr_t b0 = (uintptr_t) b->data;
362    const uintptr_t a1 = a0 + ggml_nbytes(a);
363    const uintptr_t b1 = b0 + ggml_nbytes(b);
364
365    return a0 < b1 && b0 < a1;
366}
367
368struct htp_opnode;
369
370struct ggml_hexagon_opbatch;
371struct ggml_hexagon_opqueue;
372struct ggml_hexagon_shared_buffer;
373struct ggml_hexagon_fence_buffer;
374struct ggml_hexagon_session;
375struct ggml_backend_hexagon_device_context;
376
377struct ggml_hexagon_mdev_group {
378    uint32_t idx   = 0;
379    uint32_t count = 1;
380    std::vector<std::unique_ptr<ggml_hexagon_session>> sessions;
381};
382
383struct ggml_backend_hexagon_comm_context {
384    std::vector<ggml_backend_t> backends;
385    size_t                      n_backends = 0;
386    volatile uint32_t *         fence_slots[GGML_HEXAGON_MAX_SESSIONS] = {};
387    ggml_tensor                 fence_tensors[GGML_HEXAGON_MAX_SESSIONS] = {};
388};
389
390struct ggml_hexagon_event {
391    ggml_hexagon_session * sess         = nullptr;
392    ggml_hexagon_session * fence_sess   = nullptr;
393    volatile uint32_t *    fence_slot   = nullptr;
394    ggml_tensor            fence_tensor = {};
395    uint32_t               seq          = 0;
396};
397
398struct ggml_hexagon_session {
399    std::string      name;
400    remote_handle64  handle;
401    dspqueue_t       queue;
402    uint32_t         session_id;
403    uint32_t         domain_id;
404    uint64_t         queue_id;
405    int              phys_idx;
406    int              virt_idx;
407    bool             valid_session;
408    bool             valid_handle;
409    bool             valid_queue;
410    bool             valid_iface;
411
412    ggml_hexagon_opbatch* op_batch;
413    ggml_hexagon_opqueue* op_queue;
414
415    std::unordered_map<int, std::unique_ptr<ggml_hexagon_shared_buffer>> cloned_buffers;
416    std::unordered_set<ggml_hexagon_session *>                           virt_peers;
417    std::unordered_set<ggml_hexagon_session *>                           phys_peers;
418
419    uint32_t n_threads   = 0;
420    uint32_t n_hvx       = 0;
421    uint32_t n_hmx       = 0;
422    uint64_t vtcm_size   = 0;
423    size_t   max_vmem    = 0;
424    size_t   max_bufsize = 0;
425    uint32_t fence_seq   = 0;
426
427    std::atomic<uint64_t> batch_req_seq{0};
428    std::atomic<uint64_t> batch_rsp_seq{0};
429    std::atomic<uint32_t> last_error{HTP_STATUS_OK};
430
431    uint64_t                cached_uid = 0;
432    std::vector<htp_opnode> cached_nodes;
433
434    mutable std::unordered_set<const ggml_tensor *> needs_repack;
435
436    ggml_hexagon_mdev_group                mdev;
437    ggml_backend_dev_t                     dev       = nullptr;
438    ggml_backend_hexagon_device_context *  dev_ctx   = nullptr;
439    ggml_hexagon_fence_buffer *            fence_buf = nullptr;
440
441    ggml_hexagon_session(const ggml_hexagon_device_config & config, ggml_backend_dev_t dev = nullptr, uint32_t mdev_idx = 0, uint32_t mdev_count = 0) noexcept(false);
442    ~ggml_hexagon_session() noexcept(true);
443
444    const char* c_name() const { return name.c_str(); }
445
446    void allocate(const ggml_hexagon_device_config & config) noexcept(false);
447    void release() noexcept(true);
448
449    uint8_t * alloc_fence(uint32_t n_slots = 1);
450    void      free_fence(void * ptr, uint32_t n_slots = 1);
451
452    uint8_t *                                         mdev_fence_slot = nullptr;
453    std::unordered_map<uint64_t, volatile uint32_t *> cpy_fence_slots;
454
455    void enqueue_mdev_group();
456    void enqueue_op(const htp_opnode & node);
457    void enqueue_cpy(const ggml_tensor * src, ggml_tensor * dst, const ggml_tensor * sync_tensor = nullptr, uint32_t fence_seq = 0);
458    void enqueue_fence(const ggml_tensor * sync_tensor, uint32_t fence_seq = 0, bool wait = true);
459    void enqueue_allreduce(const ggml_tensor * dst, const std::vector<const ggml_tensor *> & src_tensors,
460                           const std::vector<const ggml_tensor *> & sync_tensors, uint32_t rank, uint32_t n_ranks,
461                           uint32_t fence_seq_entry = 0, uint32_t fence_seq_exit = 0);
462
463    void flush_sync(bool all = true);
464    void flush_async();
465    void flush_batch(size_t min_ops = 1);
466    void flush_peers();
467    void flush_pending(bool all = true);
468
469    bool clone_buffer(const ggml_hexagon_shared_buffer*);
470    void release_buffer(const ggml_hexagon_shared_buffer*);
471    void unclone_buffer(const ggml_hexagon_shared_buffer*);
472
473    void add_peer(ggml_hexagon_session * peer) {
474        if (this->phys_idx == peer->phys_idx) {
475            virt_peers.insert(peer);
476        } else {
477            phys_peers.insert(peer);
478        }
479    }
480};
481
482// ** backend buffers
483
484struct ggml_backend_hexagon_device_context {
485    int                        dev_id;
486    ggml_hexagon_device_config config;
487    ggml_backend_dev_t         dev = nullptr;
488    size_t                     max_bufsize = 0;
489
490    ggml_backend_buffer_type buffer_type       = {};
491    ggml_backend_buffer_type host_buffer_type  = {};
492    ggml_backend_buffer_type fence_buffer_type = {};
493
494    std::unique_ptr<ggml_hexagon_session> sess;
495
496    ggml_backend_hexagon_device_context(int dev_id, const ggml_hexagon_device_config & config, ggml_backend_dev_t dev);
497    ~ggml_backend_hexagon_device_context();
498
499    const char * c_name() const { return config.name.c_str(); }
500
501    ggml_hexagon_session * session() {
502        if (!sess) {
503            sess = std::make_unique<ggml_hexagon_session>(config, dev);
504        }
505        return sess.get();
506    }
507};
508
509struct ggml_backend_hexagon_buffer_type_context {
510    ggml_backend_hexagon_buffer_type_context(const std::string & name, ggml_backend_hexagon_device_context * dev_ctx) {
511        this->dev_ctx = dev_ctx;
512        this->name    = name;
513    }
514
515    ggml_backend_hexagon_device_context * dev_ctx;
516    std::string                           name;
517};
518
519struct ggml_hexagon_rpcmem_block {
520    uint8_t * base = nullptr;
521    int       fd   = -1;
522    size_t    size = 0;
523
524    std::unordered_set<ggml_hexagon_session *> mapped_clones;
525
526    ggml_hexagon_rpcmem_block(size_t size) {
527        base = (uint8_t *) rpcmem_alloc2(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, size);
528        if (!base) {
529            throw std::runtime_error("ggml-hex: rpcmem_alloc failed");
530        }
531        fd = rpcmem_to_fd(base);
532        if (fd < 0) {
533            rpcmem_free(base);
534            throw std::runtime_error("ggml-hex: rpcmem_to_fd failed");
535        }
536        this->size = size;
537    }
538
539    ~ggml_hexagon_rpcmem_block() {
540        if (base) {
541            rpcmem_free(base);
542        }
543    }
544};
545
546struct ggml_hexagon_shared_buffer {
547    ggml_hexagon_session *                     sess;
548    std::shared_ptr<ggml_hexagon_rpcmem_block> mem;
549    std::vector<ggml_hexagon_tensor_extra *>   tensor_extra;
550    bool     mapped;
551    bool     pinned;
552
553    const char * c_name() const { return sess->c_name(); }
554    uint8_t *    base()   const { return mem ? mem->base : nullptr; }
555    size_t       size()   const { return mem ? mem->size : 0;  }
556    int          fd()     const { return mem ? mem->fd   : -1; }
557
558    void mmap() {
559        if (!this->mem) return;
560        fastrpc_map_flags flags = this->pinned ? FASTRPC_MAP_FD : FASTRPC_MAP_FD_DELAYED;
561
562        int err = fastrpc_mmap(sess->domain_id, fd(), (void *) base(), 0, size(), flags);
563        if (err != 0) {
564            GGML_LOG_ERROR("ggml-hex: %s buffer mapping failed : domain_id %d size %zu fd %d error 0x%08x\n", sess->c_name(),
565                    sess->domain_id, size(), fd(), (unsigned) err);
566            throw std::runtime_error("ggml-hex: fastrpc_mmap failed (see log for details)");
567        }
568
569        HEX_VERBOSE("ggml-hex: %s mapped buffer: base %p size %zu fd %d pinned %u\n",
570                sess->c_name(), (void *) base(), size(), fd(), pinned);
571
572        this->mapped = true;
573    }
574
575    void unmap() {
576        if (!this->mapped) return;
577
578        if (!this->pinned && mem) {
579            // HTP might still hold a reference, tell it drop it
580            htp_iface_munmap(sess->handle, fd());
581        }
582
583        if (mem) {
584            fastrpc_munmap(sess->domain_id, fd(), (void *) base(), size());
585        }
586
587        HEX_VERBOSE("ggml-hex: %s unmapped buffer: base %p size %zu fd %d\n", sess->c_name(),
588                (void *) base(), size(), fd());
589
590        this->mapped = false;
591    }
592
593    void alloc(size_t size) {
594        if (this->mem) return;
595
596        this->mem = std::make_shared<ggml_hexagon_rpcmem_block>(size);
597
598        HEX_VERBOSE("ggml-hex: %s allocated buffer: base %p size %zu fd %d pinned %d\n", sess->c_name(),
599                    (void *) base(), this->size(), fd(), (int) pinned);
600        mmap();
601    }
602
603    void free() {
604        unmap();
605        // The memory is freed when the shared_ptr refcount drops to 0.
606        HEX_VERBOSE("ggml-hex: %s release ref on buffer: base %p size %zu fd %d\n", sess->c_name(),
607                    (void *) base(), size(), fd());
608        this->mem  = nullptr;
609    }
610
611    ggml_hexagon_shared_buffer(ggml_hexagon_session * sess, size_t size, bool pinned = false) {
612        this->sess   = sess;
613        this->mapped = false;
614        this->pinned = pinned;
615
616        // Size adjustment inside the buffer class: 4K aligned data size + 4K guard page
617        size_t guard_offset = (size + 4095) & ~4095;
618        size_t total_size   = guard_offset + 4096;
619
620        alloc(total_size);
621    }
622
623    // Clone constructor for cross-session mapping
624    ggml_hexagon_shared_buffer(ggml_hexagon_session * sess, const ggml_hexagon_shared_buffer & other) {
625        this->sess   = sess;
626        this->mem    = other.mem;
627        this->mapped = false;
628        this->pinned = other.pinned;
629    }
630
631    ~ggml_hexagon_shared_buffer() {
632        free();
633        for (auto * extra : tensor_extra) {
634            delete extra;
635        }
636    }
637};
638
639struct ggml_hexagon_fence_buffer : public ggml_hexagon_shared_buffer {
640    uint32_t              slot_count = 0;
641    uint32_t              slot_head  = 0;
642    std::vector<uint32_t> free_slots;
643    ggml_backend_buffer   backend_buffer{};
644
645    ggml_hexagon_fence_buffer(ggml_hexagon_session * sess, ggml_backend_buffer_type_t buft, size_t size)
646        : ggml_hexagon_shared_buffer(sess, size, false /* pinned */),
647          slot_count(size / GGML_HEXAGON_FENCE_SLOT_SIZE),
648          slot_head(0) {
649        backend_buffer.buft    = buft;
650        backend_buffer.context = static_cast<ggml_hexagon_shared_buffer *>(this);
651        backend_buffer.size    = size;
652    }
653
654    uint8_t * alloc_slot(uint32_t n_slots = 1) {
655        uint8_t * ptr = nullptr;
656        if (n_slots == 1 && !free_slots.empty()) {
657            uint32_t slot = free_slots.back();
658            free_slots.pop_back();
659            ptr = base() + (size_t) slot * GGML_HEXAGON_FENCE_SLOT_SIZE;
660        } else if (slot_head + n_slots <= slot_count) {
661            uint32_t slot = slot_head;
662            slot_head += n_slots;
663            ptr = base() + (size_t) slot * GGML_HEXAGON_FENCE_SLOT_SIZE;
664        }
665        if (ptr) {
666            memset(ptr, 0, (size_t) n_slots * GGML_HEXAGON_FENCE_SLOT_SIZE);
667        }
668        return ptr;
669    }
670
671    void free_slot(void * ptr, uint32_t n_slots = 1) {
672        if (!ptr) return;
673        uint32_t slot = ((uint8_t *) ptr - base()) / GGML_HEXAGON_FENCE_SLOT_SIZE;
674        for (uint32_t i = 0; i < n_slots; i++) {
675            free_slots.push_back(slot + i);
676        }
677    }
678};
679
680inline uint8_t * ggml_hexagon_session::alloc_fence(uint32_t n_slots) {
681    uint8_t * ptr = fence_buf->alloc_slot(n_slots);
682    GGML_ASSERT(ptr);
683    return ptr;
684}
685
686inline void ggml_hexagon_session::free_fence(void * ptr, uint32_t n_slots) {
687    if (fence_buf) {
688        fence_buf->free_slot(ptr, n_slots);
689    }
690}
691
692static ggml_hexagon_session * ggml_backend_hexagon_buffer_get_sess(ggml_backend_buffer_t buffer) {
693    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(buffer->context);
694    return sbuf->sess;
695}
696
697static void ggml_backend_hexagon_buffer_free_buffer(ggml_backend_buffer_t buffer) {
698    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(buffer->context);
699    sbuf->sess->unclone_buffer(sbuf);
700    delete sbuf;
701}
702
703static void * ggml_backend_hexagon_buffer_get_base(ggml_backend_buffer_t buffer) {
704    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(buffer->context);
705    return sbuf->base();
706}
707
708static enum ggml_status ggml_backend_hexagon_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
709    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(buffer->context);
710    auto sess = sbuf->sess;
711
712    HEX_VERBOSE("ggml-hex: %s init-tensor %s : base %p data %p nbytes %zu\n", sess->c_name(),
713                tensor->name, (void *) sbuf->base(), tensor->data, ggml_nbytes(tensor));
714
715    auto extra = new ggml_hexagon_tensor_extra();
716    sbuf->tensor_extra.push_back(extra);
717
718    tensor->extra = extra;
719    if (ggml_hexagon_is_repack_type(tensor->type)) {
720        if (sess->needs_repack.count(tensor)) {
721            extra->flags |= GGML_HEXAGON_TENSOR_REPACK;
722            sess->needs_repack.erase(tensor);
723        }
724    }
725
726    return GGML_STATUS_SUCCESS;
727}
728
729// ** Repack helpers for tiled quantized weights
730
731static void unpack_q4_0_quants(uint8_t * qs, const block_q4_0 * x, unsigned int bi) {
732    static const int qk = QK4_0;
733
734    for (unsigned int i = 0; i < qk / 2; ++i) {
735        const int x0             = (x->qs[i] & 0x0F);
736        const int x1             = (x->qs[i] >> 4);
737        qs[bi * qk + i + 0]      = x0;
738        qs[bi * qk + i + qk / 2] = x1;
739    }
740}
741
742static void pack_q4_0_quants(block_q4_0 * x, const uint8_t * qs, unsigned int bi) {
743    static const int qk = QK4_0;
744
745    for (unsigned int i = 0; i < qk / 2; ++i) {
746        const uint8_t x0 = qs[bi * qk + i + 0];
747        const uint8_t x1 = qs[bi * qk + i + qk / 2];
748        x->qs[i]         = x0 | (x1 << 4);
749    }
750}
751
752static void unpack_q4_1_quants(uint8_t * qs, const block_q4_1 * x, unsigned int bi) {
753    static const int qk = QK4_1;
754
755    for (unsigned int i = 0; i < qk / 2; ++i) {
756        const int x0             = (x->qs[i] & 0x0F);
757        const int x1             = (x->qs[i] >> 4);
758        qs[bi * qk + i + 0]      = x0;
759        qs[bi * qk + i + qk / 2] = x1;
760    }
761}
762
763static void pack_q4_1_quants(block_q4_1 * x, const uint8_t * qs, unsigned int bi) {
764    static const int qk = QK4_1;
765
766    for (unsigned int i = 0; i < qk / 2; ++i) {
767        const uint8_t x0 = qs[bi * qk + i + 0];
768        const uint8_t x1 = qs[bi * qk + i + qk / 2];
769        x->qs[i]         = x0 | (x1 << 4);
770    }
771}
772
773static void unpack_mxfp4_quants(uint8_t * qs, const block_mxfp4 * x, unsigned int bi) {
774    static const int qk = QK_MXFP4;
775
776    for (unsigned int i = 0; i < qk / 2; ++i) {
777        const int x0             = (x->qs[i] & 0x0F);
778        const int x1             = (x->qs[i] >> 4);
779        qs[bi * qk + i + 0]      = x0;
780        qs[bi * qk + i + qk / 2] = x1;
781    }
782}
783
784static void pack_mxfp4_quants(block_mxfp4 * x, const uint8_t * qs, unsigned int bi) {
785    static const int qk = QK_MXFP4;
786
787    for (unsigned int i = 0; i < qk / 2; ++i) {
788        const uint8_t x0 = qs[bi * qk + i + 0];
789        const uint8_t x1 = qs[bi * qk + i + qk / 2];
790        x->qs[i]         = x0 | (x1 << 4);
791    }
792}
793
794// repack q4_0 data into q4_0_tiled tensor
795static void repack_q4_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
796    const block_q4_0 * src_matrix = (const block_q4_0 *) data;
797    int64_t ne0 = t->ne[0];
798    int64_t ne1 = t->ne[1];
799    int64_t ne2 = t->ne[2];
800    int64_t ne3 = t->ne[3];
801    int64_t ne0_padded = hex_round_up(ne0, 32);
802    int64_t ne1_padded = hex_round_up(ne1, 32);
803
804    int n_col_tiles = ne1_padded / 32;
805    int n_k_tiles = ne0_padded / 32;
806    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_Q4_0;
807    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
808
809    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
810    int64_t start_slice = offset / slice_size;
811    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
812    if (end_slice > ne2 * ne3) {
813        end_slice = ne2 * ne3;
814    }
815
816    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
817        const block_q4_0 * src_slice = src_matrix + (slice_idx - start_slice) * (ne1 * (ne0 / 32));
818        uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
819
820        for (int ct = 0; ct < n_col_tiles; ct++) {
821            for (int kt = 0; kt < n_k_tiles; kt++) {
822                uint8_t * tile_dst = matrix_dst + (ct * n_k_tiles + kt) * tile_size;
823
824                uint8_t tile_quants[32][32];
825                for (int row = 0; row < 32; row++) {
826                    int64_t r = ct * 32 + row;
827                    if (r < ne1 && kt < ne0 / 32) {
828                        unpack_q4_0_quants(tile_quants[row], &src_slice[r * (ne0 / 32) + kt], 0);
829                    } else {
830                        memset(tile_quants[row], 8, 32);
831                    }
832                }
833
834                for (int cp = 0; cp < 16; cp++) {
835                    for (int row = 0; row < 32; row++) {
836                        tile_dst[cp * 32 + row] = (tile_quants[row][2 * cp + 1] << 4) | tile_quants[row][2 * cp];
837                    }
838                }
839
840                ggml_half * scale_dst = (ggml_half *)(tile_dst + 512);
841                for (int row = 0; row < 32; row++) {
842                    int64_t r = ct * 32 + row;
843                    scale_dst[row] = (r < ne1 && kt < ne0 / 32) ? src_slice[r * (ne0 / 32) + kt].d : 0;
844                }
845            }
846        }
847    }
848}
849
850// repack q4_0_tiled tensor into q4_0 data
851static void repack_tiled_q4_0(void * data, const ggml_tensor * t, size_t offset, size_t size) {
852    block_q4_0 * dst_matrix = (block_q4_0 *) data;
853    int64_t ne0 = t->ne[0];
854    int64_t ne1 = t->ne[1];
855    int64_t ne2 = t->ne[2];
856    int64_t ne3 = t->ne[3];
857    int64_t ne0_padded = hex_round_up(ne0, 32);
858    int64_t ne1_padded = hex_round_up(ne1, 32);
859
860    int n_col_tiles = ne1_padded / 32;
861    int n_k_tiles = ne0_padded / 32;
862    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_Q4_0;
863    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
864
865    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
866    size_t row_size_bytes = ggml_row_size(t->type, ne0);
867    int64_t start_slice = offset / slice_size;
868    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
869    if (end_slice > ne2 * ne3) {
870        end_slice = ne2 * ne3;
871    }
872
873    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
874        size_t cur_start_byte = (std::max)(offset, (size_t) slice_idx * slice_size);
875        size_t cur_end_byte   = (std::min)(offset + size, (size_t) (slice_idx + 1) * slice_size);
876        size_t slice_offset_start = cur_start_byte - (size_t) slice_idx * slice_size;
877        size_t slice_offset_end   = cur_end_byte - (size_t) slice_idx * slice_size;
878
879        int64_t start_row = slice_offset_start / row_size_bytes;
880        int64_t end_row   = (slice_offset_end + row_size_bytes - 1) / row_size_bytes;
881        end_row = (std::min)(end_row, ne1);
882
883        int start_ct = start_row / 32;
884        int end_ct   = (end_row + 31) / 32;
885        end_ct = (std::min)(end_ct, n_col_tiles);
886
887        block_q4_0 * dst_slice = dst_matrix + (cur_start_byte - offset) / sizeof(block_q4_0);
888        const uint8_t * matrix_src = (const uint8_t *) t->data + slice_idx * matrix_size;
889
890        for (int ct = start_ct; ct < end_ct; ct++) {
891            for (int kt = 0; kt < n_k_tiles; kt++) {
892                const uint8_t * tile_src = matrix_src + (ct * n_k_tiles + kt) * tile_size;
893
894                uint8_t tile_quants[32][32];
895                for (int cp = 0; cp < 16; cp++) {
896                    for (int row = 0; row < 32; row++) {
897                        uint8_t val = tile_src[cp * 32 + row];
898                        tile_quants[row][2 * cp + 0] = val & 0x0F;
899                        tile_quants[row][2 * cp + 1] = val >> 4;
900                    }
901                }
902
903                for (int row = 0; row < 32; row++) {
904                    int64_t r = ct * 32 + row;
905                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
906                        pack_q4_0_quants(&dst_slice[(r - start_row) * (ne0 / 32) + kt], tile_quants[row], 0);
907                    }
908                }
909
910                const ggml_half * scale_src = (const ggml_half *)(tile_src + 512);
911                for (int row = 0; row < 32; row++) {
912                    int64_t r = ct * 32 + row;
913                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
914                        dst_slice[(r - start_row) * (ne0 / 32) + kt].d = scale_src[row];
915                    }
916                }
917            }
918        }
919    }
920}
921
922// repack q4_1 data into q4_1_tiled tensor
923static void repack_q4_1_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
924    const block_q4_1 * src_matrix = (const block_q4_1 *) data;
925    int64_t ne0 = t->ne[0];
926    int64_t ne1 = t->ne[1];
927    int64_t ne2 = t->ne[2];
928    int64_t ne3 = t->ne[3];
929    int64_t ne0_padded = hex_round_up(ne0, 32);
930    int64_t ne1_padded = hex_round_up(ne1, 32);
931
932    int n_col_tiles = ne1_padded / 32;
933    int n_k_tiles = ne0_padded / 32;
934    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_Q4_1;
935    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
936
937    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
938    int64_t start_slice = offset / slice_size;
939    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
940    if (end_slice > ne2 * ne3) {
941        end_slice = ne2 * ne3;
942    }
943
944    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
945        const block_q4_1 * src_slice = src_matrix + (slice_idx - start_slice) * (ne1 * (ne0 / 32));
946        uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
947
948        for (int ct = 0; ct < n_col_tiles; ct++) {
949            for (int kt = 0; kt < n_k_tiles; kt++) {
950                uint8_t * tile_dst = matrix_dst + (ct * n_k_tiles + kt) * tile_size;
951
952                uint8_t tile_quants[32][32];
953                for (int row = 0; row < 32; row++) {
954                    int64_t r = ct * 32 + row;
955                    if (r < ne1 && kt < ne0 / 32) {
956                        unpack_q4_1_quants(tile_quants[row], &src_slice[r * (ne0 / 32) + kt], 0);
957                    } else {
958                        memset(tile_quants[row], 0, 32);
959                    }
960                }
961
962                for (int cp = 0; cp < 16; cp++) {
963                    for (int row = 0; row < 32; row++) {
964                        tile_dst[cp * 32 + row] = (tile_quants[row][2 * cp + 1] << 4) | tile_quants[row][2 * cp];
965                    }
966                }
967
968                ggml_half * scale_dst = (ggml_half *)(tile_dst + 512);
969                for (int row = 0; row < 32; row++) {
970                    int64_t r = ct * 32 + row;
971                    if (r < ne1 && kt < ne0 / 32) {
972                        scale_dst[2 * row + 0] = src_slice[r * (ne0 / 32) + kt].d;
973                        scale_dst[2 * row + 1] = src_slice[r * (ne0 / 32) + kt].m;
974                    } else {
975                        scale_dst[2 * row + 0] = 0;
976                        scale_dst[2 * row + 1] = 0;
977                    }
978                }
979            }
980        }
981    }
982}
983
984// repack q4_1_tiled tensor into q4_1 data
985static void repack_tiled_q4_1(void * data, const ggml_tensor * t, size_t offset, size_t size) {
986    block_q4_1 * dst_matrix = (block_q4_1 *) data;
987    int64_t ne0 = t->ne[0];
988    int64_t ne1 = t->ne[1];
989    int64_t ne2 = t->ne[2];
990    int64_t ne3 = t->ne[3];
991    int64_t ne0_padded = hex_round_up(ne0, 32);
992    int64_t ne1_padded = hex_round_up(ne1, 32);
993
994    int n_col_tiles = ne1_padded / 32;
995    int n_k_tiles = ne0_padded / 32;
996    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_Q4_1;
997    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
998
999    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
1000    size_t row_size_bytes = ggml_row_size(t->type, ne0);
1001    int64_t start_slice = offset / slice_size;
1002    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
1003    if (end_slice > ne2 * ne3) {
1004        end_slice = ne2 * ne3;
1005    }
1006
1007    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
1008        size_t cur_start_byte = (std::max)(offset, (size_t) slice_idx * slice_size);
1009        size_t cur_end_byte   = (std::min)(offset + size, (size_t) (slice_idx + 1) * slice_size);
1010        size_t slice_offset_start = cur_start_byte - (size_t) slice_idx * slice_size;
1011        size_t slice_offset_end   = cur_end_byte - (size_t) slice_idx * slice_size;
1012
1013        int64_t start_row = slice_offset_start / row_size_bytes;
1014        int64_t end_row   = (slice_offset_end + row_size_bytes - 1) / row_size_bytes;
1015        end_row = (std::min)(end_row, ne1);
1016
1017        int start_ct = start_row / 32;
1018        int end_ct   = (end_row + 31) / 32;
1019        end_ct = (std::min)(end_ct, n_col_tiles);
1020
1021        block_q4_1 * dst_slice = dst_matrix + (cur_start_byte - offset) / sizeof(block_q4_1);
1022        const uint8_t * matrix_src = (const uint8_t *) t->data + slice_idx * matrix_size;
1023
1024        for (int ct = start_ct; ct < end_ct; ct++) {
1025            for (int kt = 0; kt < n_k_tiles; kt++) {
1026                const uint8_t * tile_src = matrix_src + (ct * n_k_tiles + kt) * tile_size;
1027
1028                uint8_t tile_quants[32][32];
1029                for (int cp = 0; cp < 16; cp++) {
1030                    for (int row = 0; row < 32; row++) {
1031                        uint8_t val = tile_src[cp * 32 + row];
1032                        tile_quants[row][2 * cp + 0] = val & 0x0F;
1033                        tile_quants[row][2 * cp + 1] = val >> 4;
1034                    }
1035                }
1036
1037                for (int row = 0; row < 32; row++) {
1038                    int64_t r = ct * 32 + row;
1039                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
1040                        pack_q4_1_quants(&dst_slice[(r - start_row) * (ne0 / 32) + kt], tile_quants[row], 0);
1041                    }
1042                }
1043
1044                const ggml_half * scale_src = (const ggml_half *)(tile_src + 512);
1045                for (int row = 0; row < 32; row++) {
1046                    int64_t r = ct * 32 + row;
1047                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
1048                        dst_slice[(r - start_row) * (ne0 / 32) + kt].d = scale_src[2 * row];
1049                        dst_slice[(r - start_row) * (ne0 / 32) + kt].m = scale_src[2 * row + 1];
1050                    }
1051                }
1052            }
1053        }
1054    }
1055}
1056
1057// repack q8_0 data into q8_0_tiled tensor
1058static void repack_q8_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
1059    const block_q8_0 * src_matrix = (const block_q8_0 *) data;
1060    int64_t ne0 = t->ne[0];
1061    int64_t ne1 = t->ne[1];
1062    int64_t ne2 = t->ne[2];
1063    int64_t ne3 = t->ne[3];
1064    int64_t ne0_padded = hex_round_up(ne0, 32);
1065    int64_t ne1_padded = hex_round_up(ne1, 32);
1066
1067    int n_col_tiles = ne1_padded / 32;
1068    int n_k_tiles = ne0_padded / 32;
1069    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_Q8_0;
1070    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
1071
1072    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
1073    int64_t start_slice = offset / slice_size;
1074    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
1075    if (end_slice > ne2 * ne3) {
1076        end_slice = ne2 * ne3;
1077    }
1078
1079    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
1080        const block_q8_0 * src_slice = src_matrix + (slice_idx - start_slice) * (ne1 * (ne0 / 32));
1081        uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
1082
1083        for (int ct = 0; ct < n_col_tiles; ct++) {
1084            for (int kt = 0; kt < n_k_tiles; kt++) {
1085                uint8_t * tile_dst = matrix_dst + (ct * n_k_tiles + kt) * tile_size;
1086
1087                for (int cp = 0; cp < 16; cp++) {
1088                    int col0 = cp * 2;
1089                    int col1 = col0 + 1;
1090                    for (int row = 0; row < 32; row++) {
1091                        int64_t r = ct * 32 + row;
1092                        const block_q8_0 * b = (r < ne1 && kt < ne0 / 32) ? &src_slice[r * (ne0 / 32) + kt] : NULL;
1093                        tile_dst[cp * 64 + 2 * row + 0] = b ? b->qs[col0] : 0;
1094                        tile_dst[cp * 64 + 2 * row + 1] = b ? b->qs[col1] : 0;
1095                    }
1096                }
1097
1098                ggml_half * scale_dst = (ggml_half *)(tile_dst + 1024);
1099                for (int row = 0; row < 32; row++) {
1100                    int64_t r = ct * 32 + row;
1101                    scale_dst[row] = (r < ne1 && kt < ne0 / 32) ? src_slice[r * (ne0 / 32) + kt].d : 0;
1102                }
1103            }
1104        }
1105    }
1106}
1107
1108// repack q8_0_tiled tensor into q8_0 data
1109static void repack_tiled_q8_0(void * data, const ggml_tensor * t, size_t offset, size_t size) {
1110    block_q8_0 * dst_matrix = (block_q8_0 *) data;
1111    int64_t ne0 = t->ne[0];
1112    int64_t ne1 = t->ne[1];
1113    int64_t ne2 = t->ne[2];
1114    int64_t ne3 = t->ne[3];
1115    int64_t ne0_padded = hex_round_up(ne0, 32);
1116    int64_t ne1_padded = hex_round_up(ne1, 32);
1117
1118    int n_col_tiles = ne1_padded / 32;
1119    int n_k_tiles = ne0_padded / 32;
1120    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_Q8_0;
1121    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
1122
1123    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
1124    size_t row_size_bytes = ggml_row_size(t->type, ne0);
1125    int64_t start_slice = offset / slice_size;
1126    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
1127    if (end_slice > ne2 * ne3) {
1128        end_slice = ne2 * ne3;
1129    }
1130
1131    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
1132        size_t cur_start_byte = (std::max)(offset, (size_t) slice_idx * slice_size);
1133        size_t cur_end_byte   = (std::min)(offset + size, (size_t) (slice_idx + 1) * slice_size);
1134        size_t slice_offset_start = cur_start_byte - (size_t) slice_idx * slice_size;
1135        size_t slice_offset_end   = cur_end_byte - (size_t) slice_idx * slice_size;
1136
1137        int64_t start_row = slice_offset_start / row_size_bytes;
1138        int64_t end_row   = (slice_offset_end + row_size_bytes - 1) / row_size_bytes;
1139        end_row = (std::min)(end_row, ne1);
1140
1141        int start_ct = start_row / 32;
1142        int end_ct   = (end_row + 31) / 32;
1143        end_ct = (std::min)(end_ct, n_col_tiles);
1144
1145        block_q8_0 * dst_slice = dst_matrix + (cur_start_byte - offset) / sizeof(block_q8_0);
1146        const uint8_t * matrix_src = (const uint8_t *) t->data + slice_idx * matrix_size;
1147
1148        for (int ct = start_ct; ct < end_ct; ct++) {
1149            for (int kt = 0; kt < n_k_tiles; kt++) {
1150                const uint8_t * tile_src = matrix_src + (ct * n_k_tiles + kt) * tile_size;
1151
1152                for (int cp = 0; cp < 16; cp++) {
1153                    int col0 = cp * 2;
1154                    int col1 = col0 + 1;
1155                    for (int row = 0; row < 32; row++) {
1156                        int64_t r = ct * 32 + row;
1157                        if (r >= start_row && r < end_row && kt < ne0 / 32) {
1158                            block_q8_0 & b = dst_slice[(r - start_row) * (ne0 / 32) + kt];
1159                            b.qs[col0] = tile_src[cp * 64 + 2 * row + 0];
1160                            b.qs[col1] = tile_src[cp * 64 + 2 * row + 1];
1161                        }
1162                    }
1163                }
1164
1165                const ggml_half * scale_src = (const ggml_half *)(tile_src + 1024);
1166                for (int row = 0; row < 32; row++) {
1167                    int64_t r = ct * 32 + row;
1168                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
1169                        dst_slice[(r - start_row) * (ne0 / 32) + kt].d = scale_src[row];
1170                    }
1171                }
1172            }
1173        }
1174    }
1175}
1176
1177// repack mxfp4 data into mxfp4_tiled tensor
1178static void repack_mxfp4_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
1179    const block_mxfp4 * src_matrix = (const block_mxfp4 *) data;
1180    int64_t ne0 = t->ne[0];
1181    int64_t ne1 = t->ne[1];
1182    int64_t ne2 = t->ne[2];
1183    int64_t ne3 = t->ne[3];
1184    int64_t ne0_padded = hex_round_up(ne0, 32);
1185    int64_t ne1_padded = hex_round_up(ne1, 32);
1186
1187    int n_col_tiles = ne1_padded / 32;
1188    int n_k_tiles = ne0_padded / 32;
1189    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_MXFP4;
1190    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
1191
1192    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
1193    int64_t start_slice = offset / slice_size;
1194    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
1195    if (end_slice > ne2 * ne3) {
1196        end_slice = ne2 * ne3;
1197    }
1198
1199    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
1200        const block_mxfp4 * src_slice = src_matrix + (slice_idx - start_slice) * (ne1 * (ne0 / 32));
1201        uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
1202
1203        for (int ct = 0; ct < n_col_tiles; ct++) {
1204            for (int kt = 0; kt < n_k_tiles; kt++) {
1205                uint8_t * tile_dst = matrix_dst + (ct * n_k_tiles + kt) * tile_size;
1206
1207                uint8_t tile_quants[32][32];
1208                for (int row = 0; row < 32; row++) {
1209                    int64_t r = ct * 32 + row;
1210                    if (r < ne1 && kt < ne0 / 32) {
1211                        unpack_mxfp4_quants(tile_quants[row], &src_slice[r * (ne0 / 32) + kt], 0);
1212                    } else {
1213                        memset(tile_quants[row], 0, 32);
1214                    }
1215                }
1216
1217                for (int cp = 0; cp < 16; cp++) {
1218                    for (int row = 0; row < 32; row++) {
1219                        tile_dst[cp * 32 + row] = (tile_quants[row][2 * cp + 1] << 4) | tile_quants[row][2 * cp];
1220                    }
1221                }
1222
1223                uint8_t * scale_dst = tile_dst + 512;
1224                for (int row = 0; row < 32; row++) {
1225                    int64_t r = ct * 32 + row;
1226                    scale_dst[row] = (r < ne1 && kt < ne0 / 32) ? src_slice[r * (ne0 / 32) + kt].e : 0;
1227                }
1228            }
1229        }
1230    }
1231}
1232
1233// repack mxfp4_tiled tensor into mxfp4 data
1234static void repack_tiled_mxfp4(void * data, const ggml_tensor * t, size_t offset, size_t size) {
1235    block_mxfp4 * dst_matrix = (block_mxfp4 *) data;
1236    int64_t ne0 = t->ne[0];
1237    int64_t ne1 = t->ne[1];
1238    int64_t ne2 = t->ne[2];
1239    int64_t ne3 = t->ne[3];
1240    int64_t ne0_padded = hex_round_up(ne0, 32);
1241    int64_t ne1_padded = hex_round_up(ne1, 32);
1242
1243    int n_col_tiles = ne1_padded / 32;
1244    int n_k_tiles = ne0_padded / 32;
1245    const size_t tile_size = HTP_MM_WEIGHT_TILE_SIZE_MXFP4;
1246    const size_t matrix_size = n_col_tiles * n_k_tiles * tile_size;
1247
1248    size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
1249    size_t row_size_bytes = ggml_row_size(t->type, ne0);
1250    int64_t start_slice = offset / slice_size;
1251    int64_t end_slice = (offset + size + slice_size - 1) / slice_size;
1252    if (end_slice > ne2 * ne3) {
1253        end_slice = ne2 * ne3;
1254    }
1255
1256    for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
1257        size_t cur_start_byte = (std::max)(offset, (size_t) slice_idx * slice_size);
1258        size_t cur_end_byte   = (std::min)(offset + size, (size_t) (slice_idx + 1) * slice_size);
1259        size_t slice_offset_start = cur_start_byte - (size_t) slice_idx * slice_size;
1260        size_t slice_offset_end   = cur_end_byte - (size_t) slice_idx * slice_size;
1261
1262        int64_t start_row = slice_offset_start / row_size_bytes;
1263        int64_t end_row   = (slice_offset_end + row_size_bytes - 1) / row_size_bytes;
1264        end_row = (std::min)(end_row, ne1);
1265
1266        int start_ct = start_row / 32;
1267        int end_ct   = (end_row + 31) / 32;
1268        end_ct = (std::min)(end_ct, n_col_tiles);
1269
1270        block_mxfp4 * dst_slice = dst_matrix + (cur_start_byte - offset) / sizeof(block_mxfp4);
1271        const uint8_t * matrix_src = (const uint8_t *) t->data + slice_idx * matrix_size;
1272
1273        for (int ct = start_ct; ct < end_ct; ct++) {
1274            for (int kt = 0; kt < n_k_tiles; kt++) {
1275                const uint8_t * tile_src = matrix_src + (ct * n_k_tiles + kt) * tile_size;
1276
1277                uint8_t tile_quants[32][32];
1278                for (int cp = 0; cp < 16; cp++) {
1279                    for (int row = 0; row < 32; row++) {
1280                        uint8_t val = tile_src[cp * 32 + row];
1281                        tile_quants[row][2 * cp + 0] = val & 0x0F;
1282                        tile_quants[row][2 * cp + 1] = val >> 4;
1283                    }
1284                }
1285
1286                for (int row = 0; row < 32; row++) {
1287                    int64_t r = ct * 32 + row;
1288                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
1289                        pack_mxfp4_quants(&dst_slice[(r - start_row) * (ne0 / 32) + kt], tile_quants[row], 0);
1290                    }
1291                }
1292
1293                const uint8_t * scale_src = tile_src + 512;
1294                for (int row = 0; row < 32; row++) {
1295                    int64_t r = ct * 32 + row;
1296                    if (r >= start_row && r < end_row && kt < ne0 / 32) {
1297                        dst_slice[(r - start_row) * (ne0 / 32) + kt].e = scale_src[row];
1298                    }
1299                }
1300            }
1301        }
1302    }
1303}
1304
1305static void repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t size) {
1306    switch (tensor->type) {
1307        case GGML_TYPE_Q4_0:
1308            repack_q4_0_tiled(tensor, data, 0, size);
1309            break;
1310
1311        case GGML_TYPE_Q4_1:
1312            repack_q4_1_tiled(tensor, data, 0, size);
1313            break;
1314
1315        case GGML_TYPE_Q8_0:
1316            repack_q8_0_tiled(tensor, data, 0, size);
1317            break;
1318
1319        case GGML_TYPE_IQ4_NL:
1320            repack_q4_0_tiled(tensor, data, 0, size);
1321            break;
1322
1323        case GGML_TYPE_MXFP4:
1324            repack_mxfp4_tiled(tensor, data, 0, size);
1325            break;
1326
1327        default:
1328            break;
1329    }
1330}
1331
1332static void ggml_backend_hexagon_buffer_set_tensor(ggml_backend_buffer_t buffer,
1333                                                   ggml_tensor *         tensor,
1334                                                   const void *          data,
1335                                                   size_t                offset,
1336                                                   size_t                size) {
1337    auto extra = (ggml_hexagon_tensor_extra *)  tensor->extra;
1338    auto sbuf  = (ggml_hexagon_shared_buffer *) buffer->context;
1339    auto sess  = sbuf->sess;
1340
1341    if (ggml_backend_buffer_get_usage(buffer) == GGML_BACKEND_BUFFER_USAGE_WEIGHTS) {
1342        extra->flags |= GGML_HEXAGON_TENSOR_WEIGHT;
1343        if (ggml_hexagon_is_repack_type(tensor->type)) {
1344            extra->flags |= GGML_HEXAGON_TENSOR_REPACK;
1345        }
1346    }
1347
1348    HEX_VERBOSE("ggml-hex: %s set-tensor %s : data %p offset %zu size %zu usage %d flags 0x%x\n",
1349        sess->c_name(), tensor->name, data, offset, size, (int) buffer->usage, extra->flags);
1350
1351    if ((extra->flags & GGML_HEXAGON_TENSOR_REPACK) == 0) {
1352        memcpy((char *) tensor->data + offset, data, size);
1353        return;
1354    }
1355
1356    if (offset == 0 && size == ggml_nbytes(tensor) && extra->shadow_buf.empty()) {
1357        repack_tensor_tiled(tensor, data, size);
1358        return;
1359    }
1360
1361    if (extra->shadow_buf.size() < ggml_nbytes(tensor)) {
1362        extra->shadow_buf.resize(ggml_nbytes(tensor));
1363    }
1364    memcpy(extra->shadow_buf.data() + offset, data, size);
1365    extra->shadow_size += size;
1366
1367    if (extra->shadow_size >= ggml_nbytes(tensor)) {
1368        repack_tensor_tiled(tensor, extra->shadow_buf.data(), extra->shadow_buf.size());
1369        extra->shadow_buf.clear();
1370        extra->shadow_buf.shrink_to_fit();
1371        extra->shadow_size = 0;
1372    }
1373}
1374
1375static void ggml_backend_hexagon_buffer_get_tensor(ggml_backend_buffer_t buffer,
1376                                                   const ggml_tensor *   tensor,
1377                                                   void *                data,
1378                                                   size_t                offset,
1379                                                   size_t                size) {
1380    auto extra = (ggml_hexagon_tensor_extra *)  tensor->extra;
1381    auto sbuf  = (ggml_hexagon_shared_buffer *) buffer->context;
1382    auto sess  = sbuf->sess;
1383
1384    HEX_VERBOSE("ggml-hex: %s get-tensor %s : data %p offset %zu size %zu usage %d flags 0x%x\n",
1385            sess->c_name(), tensor->name, data, offset, size, (int) buffer->usage, extra->flags);
1386
1387    if ((extra->flags & GGML_HEXAGON_TENSOR_REPACK) == 0) {
1388        memcpy(data, (const char *) tensor->data + offset, size);
1389        return;
1390    }
1391
1392    switch (tensor->type) {
1393        case GGML_TYPE_Q4_0:
1394            GGML_ASSERT(offset == 0);
1395            GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
1396            repack_tiled_q4_0(data, tensor, offset, size);
1397            break;
1398
1399        case GGML_TYPE_Q4_1:
1400            GGML_ASSERT(offset == 0);
1401            GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
1402            repack_tiled_q4_1(data, tensor, offset, size);
1403            break;
1404
1405        case GGML_TYPE_Q8_0:
1406            GGML_ASSERT(offset == 0);
1407            GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
1408            repack_tiled_q8_0(data, tensor, offset, size);
1409            break;
1410
1411        case GGML_TYPE_IQ4_NL:
1412            GGML_ASSERT(offset == 0);
1413            GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
1414            repack_tiled_q4_0(data, tensor, offset, size);
1415            break;
1416
1417        case GGML_TYPE_MXFP4:
1418            GGML_ASSERT(offset == 0);
1419            GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
1420            repack_tiled_mxfp4(data, tensor, offset, size);
1421            break;
1422
1423        default:
1424            memcpy(data, (const char *) tensor->data + offset, size);
1425            break;
1426    }
1427}
1428
1429static bool ggml_backend_hexagon_buffer_cpy_tensor(ggml_backend_buffer_t      buffer,
1430                                                   const struct ggml_tensor * src,
1431                                                   struct ggml_tensor *       dst) {
1432    // we might optimize this later, for now take the slow path (ie get/set_tensor)
1433    return false;
1434
1435    GGML_UNUSED(buffer);
1436    GGML_UNUSED(src);
1437    GGML_UNUSED(dst);
1438}
1439
1440static void ggml_backend_hexagon_buffer_set_tensor_2d(ggml_backend_buffer_t buffer,
1441                                                          ggml_tensor *         tensor,
1442                                                          const void *          data,
1443                                                          size_t                offset,
1444                                                          size_t                size,
1445                                                          size_t                n_copies,
1446                                                          size_t                stride_tensor,
1447                                                          size_t                stride_data) {
1448    auto extra = (ggml_hexagon_tensor_extra *)  tensor->extra;
1449    auto sbuf  = (ggml_hexagon_shared_buffer *) buffer->context;
1450    auto sess  = sbuf->sess;
1451
1452    if (ggml_backend_buffer_get_usage(buffer) == GGML_BACKEND_BUFFER_USAGE_WEIGHTS) {
1453        extra->flags |= GGML_HEXAGON_TENSOR_WEIGHT;
1454        if (ggml_hexagon_is_repack_type(tensor->type)) {
1455            extra->flags |= GGML_HEXAGON_TENSOR_REPACK;
1456        }
1457    }
1458
1459    HEX_VERBOSE("ggml-hex: %s set-tensor-2d %s : data %p offset %zu size %zu n_copies %zu stride_tensor %zu stride_data %zu usage %d flags 0x%x\n",
1460                sess->c_name(), tensor->name, data, offset, size, n_copies, stride_tensor, stride_data, (int) buffer->usage, extra->flags);
1461
1462    if ((extra->flags & GGML_HEXAGON_TENSOR_REPACK) == 0) {
1463        for (size_t i = 0; i < n_copies; i++) {
1464            memcpy((uint8_t *) tensor->data + offset + i * stride_tensor, (const uint8_t *) data + i * stride_data, size);
1465        }
1466        return;
1467    }
1468
1469    if (extra->shadow_buf.size() < ggml_nbytes(tensor)) {
1470        extra->shadow_buf.resize(ggml_nbytes(tensor));
1471    }
1472    for (size_t i = 0; i < n_copies; i++) {
1473        memcpy(extra->shadow_buf.data() + offset + i * stride_tensor, (const uint8_t *) data + i * stride_data, size);
1474    }
1475    extra->shadow_size += n_copies * size;
1476
1477    if (extra->shadow_size >= ggml_nbytes(tensor)) {
1478        repack_tensor_tiled(tensor, extra->shadow_buf.data(), extra->shadow_buf.size());
1479        extra->shadow_buf.clear();
1480        extra->shadow_buf.shrink_to_fit();
1481        extra->shadow_size = 0;
1482    }
1483}
1484
1485static void ggml_backend_hexagon_buffer_get_tensor_2d(ggml_backend_buffer_t buffer,
1486                                                      const ggml_tensor *   tensor,
1487                                                      void *                data,
1488                                                      size_t                offset,
1489                                                      size_t                size,
1490                                                      size_t                n_copies,
1491                                                      size_t                stride_tensor,
1492                                                      size_t                stride_data) {
1493    auto extra = (ggml_hexagon_tensor_extra *)  tensor->extra;
1494    auto sbuf  = (ggml_hexagon_shared_buffer *) buffer->context;
1495    auto sess  = sbuf->sess;
1496
1497    HEX_VERBOSE("ggml-hex: %s get-tensor-2d %s : data %p offset %zu size %zu n_copies %zu stride_tensor %zu stride_data %zu usage %d\n",
1498                sess->c_name(), tensor->name, data, offset, size, n_copies, stride_tensor, stride_data, (int) buffer->usage);
1499
1500    if ((extra->flags & GGML_HEXAGON_TENSOR_REPACK) == 0) {
1501        for (size_t i = 0; i < n_copies; i++) {
1502            memcpy((uint8_t *)data + i * stride_data, (const uint8_t *)tensor->data + offset + i * stride_tensor, size);
1503        }
1504        return;
1505    }
1506
1507    size_t temp_size      = n_copies > 0 ? (n_copies - 1) * stride_tensor + size : 0;
1508    size_t slice_size     = tensor->ne[1] * ggml_row_size(tensor->type, tensor->ne[0]);
1509    size_t slice_offset   = offset % slice_size;
1510    size_t row_size_bytes = ggml_row_size(tensor->type, tensor->ne[0]);
1511
1512    GGML_ASSERT((slice_offset % row_size_bytes) == 0 && "offset must be aligned to row boundary");
1513    GGML_ASSERT((temp_size % row_size_bytes)    == 0 && "temp_size must be a multiple of row size");
1514    GGML_ASSERT((slice_offset / row_size_bytes) % 32 == 0 && "offset must be aligned to tile size (32 rows)");
1515    GGML_ASSERT((offset + temp_size) <= ggml_nbytes(tensor));
1516
1517    std::vector<uint8_t> temp_buf(temp_size);
1518
1519    switch (tensor->type) {
1520        case GGML_TYPE_Q4_0:
1521            repack_tiled_q4_0(temp_buf.data(), tensor, offset, temp_size);
1522            break;
1523
1524        case GGML_TYPE_Q4_1:
1525            repack_tiled_q4_1(temp_buf.data(), tensor, offset, temp_size);
1526            break;
1527
1528        case GGML_TYPE_Q8_0:
1529            repack_tiled_q8_0(temp_buf.data(), tensor, offset, temp_size);
1530            break;
1531
1532        case GGML_TYPE_IQ4_NL:
1533            repack_tiled_q4_0(temp_buf.data(), tensor, offset, temp_size);
1534            break;
1535
1536        case GGML_TYPE_MXFP4:
1537            repack_tiled_mxfp4(temp_buf.data(), tensor, offset, temp_size);
1538            break;
1539
1540        default:
1541            memcpy(temp_buf.data(), (const uint8_t *) tensor->data + offset, temp_size);
1542            break;
1543    }
1544
1545    for (size_t i = 0; i < n_copies; i++) {
1546        memcpy((uint8_t *) data + i * stride_data, temp_buf.data() + i * stride_tensor, size);
1547    }
1548}
1549
1550static void ggml_backend_hexagon_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) {
1551    auto sbuf = (ggml_hexagon_shared_buffer *) buffer->context;
1552    auto sess = sbuf->sess;
1553    HEX_VERBOSE("ggml-hex: %s clear-buff base %p size %zu\n", sess->c_name(), (void *) sbuf->base(), sbuf->size());
1554    memset(sbuf->base(), value, sbuf->size());
1555}
1556
1557static ggml_backend_buffer_i ggml_backend_hexagon_buffer_interface = {
1558    /* .free_buffer     = */ ggml_backend_hexagon_buffer_free_buffer,
1559    /* .get_base        = */ ggml_backend_hexagon_buffer_get_base,
1560    /* .init_tensor     = */ ggml_backend_hexagon_buffer_init_tensor,
1561    /* .memset_tensor   = */ NULL,
1562    /* .set_tensor      = */ ggml_backend_hexagon_buffer_set_tensor,
1563    /* .get_tensor      = */ ggml_backend_hexagon_buffer_get_tensor,
1564    /* .set_tensor_2d   = */ ggml_backend_hexagon_buffer_set_tensor_2d,
1565    /* .get_tensor_2d   = */ ggml_backend_hexagon_buffer_get_tensor_2d,
1566    /* .cpy_tensor      = */ ggml_backend_hexagon_buffer_cpy_tensor,
1567    /* .clear           = */ ggml_backend_hexagon_buffer_clear,
1568    /* .reset           = */ NULL,
1569};
1570
1571// ** backend buffer type
1572
1573static void ggml_backend_hexagon_host_buffer_set_tensor(ggml_backend_buffer_t buffer,
1574                                                        ggml_tensor *         tensor,
1575                                                        const void *          data,
1576                                                        size_t                offset,
1577                                                        size_t                size) {
1578    memcpy((char *) tensor->data + offset, data, size);
1579    GGML_UNUSED(buffer);
1580}
1581
1582static void ggml_backend_hexagon_host_buffer_get_tensor(ggml_backend_buffer_t buffer,
1583                                                        const ggml_tensor *   tensor,
1584                                                        void *                data,
1585                                                        size_t                offset,
1586                                                        size_t                size) {
1587    memcpy(data, (const char *) tensor->data + offset, size);
1588    GGML_UNUSED(buffer);
1589}
1590
1591static ggml_backend_buffer_i ggml_backend_hexagon_host_buffer_interface = {
1592    /* .free_buffer     = */ ggml_backend_hexagon_buffer_free_buffer,
1593    /* .get_base        = */ ggml_backend_hexagon_buffer_get_base,
1594    /* .init_tensor     = */ ggml_backend_hexagon_buffer_init_tensor,
1595    /* .memset_tensor   = */ NULL,
1596    /* .set_tensor      = */ ggml_backend_hexagon_host_buffer_set_tensor,
1597    /* .get_tensor      = */ ggml_backend_hexagon_host_buffer_get_tensor,
1598    /* .set_tensor_2d   = */ NULL,
1599    /* .get_tensor_2d   = */ NULL,
1600    /* .cpy_tensor      = */ ggml_backend_hexagon_buffer_cpy_tensor,
1601    /* .clear           = */ ggml_backend_hexagon_buffer_clear,
1602    /* .reset           = */ NULL,
1603};
1604
1605// ** backend buffer type
1606
1607static const char * ggml_backend_hexagon_buffer_type_name(ggml_backend_buffer_type_t buffer_type) {
1608    return static_cast<ggml_backend_hexagon_buffer_type_context *>(buffer_type->context)->name.c_str();
1609}
1610
1611static ggml_backend_buffer_t ggml_backend_hexagon_buffer_type_alloc_buffer(
1612            ggml_backend_buffer_type_t buffer_type, size_t size) {
1613    auto dev_ctx = static_cast<ggml_backend_hexagon_buffer_type_context *>(buffer_type->context)->dev_ctx;
1614    auto sess    = dev_ctx->session();
1615    try {
1616        ggml_hexagon_shared_buffer * sbuf = new ggml_hexagon_shared_buffer(sess, size, false);
1617        return ggml_backend_buffer_init(buffer_type, ggml_backend_hexagon_buffer_interface, sbuf, size);
1618    } catch (const std::exception & exc) {
1619        GGML_LOG_ERROR("ggml-hex: %s failed to allocate device buffer context: %s\n", dev_ctx->c_name(), exc.what());
1620        return nullptr;
1621    }
1622}
1623
1624static ggml_backend_buffer_t ggml_backend_hexagon_host_buffer_type_alloc_buffer(
1625            ggml_backend_buffer_type_t buffer_type, size_t size) {
1626    auto dev_ctx = static_cast<ggml_backend_hexagon_buffer_type_context *>(buffer_type->context)->dev_ctx;
1627    auto sess    = dev_ctx->session();
1628    try {
1629        ggml_hexagon_shared_buffer * sbuf = new ggml_hexagon_shared_buffer(sess, size, false);
1630        return ggml_backend_buffer_init(buffer_type, ggml_backend_hexagon_host_buffer_interface, sbuf, size);
1631    } catch (const std::exception & exc) {
1632        GGML_LOG_ERROR("ggml-hex: %s failed to allocate host buffer context: %s\n", dev_ctx->c_name(), exc.what());
1633        return nullptr;
1634    }
1635}
1636
1637static size_t ggml_backend_hexagon_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) {
1638    return 128;  // HVX alignment
1639    GGML_UNUSED(buft);
1640}
1641
1642static size_t ggml_backend_hexagon_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const struct ggml_tensor * t) {
1643    if (ggml_hexagon_is_repack_type(t->type)) {
1644        int64_t ne0 = hex_round_up(t->ne[0], 32);
1645        int64_t ne1 = hex_round_up(t->ne[1], 32);
1646        int64_t ne2 = t->ne[2];
1647        int64_t ne3 = t->ne[3];
1648        return ggml_row_size(t->type, ne0) * ne1 * ne2 * ne3;
1649    }
1650    return ggml_nbytes(t);
1651
1652    GGML_UNUSED(buft);
1653}
1654
1655static size_t ggml_backend_hexagon_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
1656    auto * context = static_cast<ggml_backend_hexagon_buffer_type_context *>(buft->context);
1657    return context->dev_ctx->max_bufsize;
1658}
1659
1660static bool ggml_backend_hexagon_buffer_type_is_host(ggml_backend_buffer_type_t buft) {
1661    return false;
1662    GGML_UNUSED(buft);
1663}
1664
1665static bool ggml_backend_hexagon_host_buffer_type_is_host(ggml_backend_buffer_type_t buft) {
1666    return true;
1667    GGML_UNUSED(buft);
1668}
1669
1670static ggml_backend_buffer_type_i ggml_backend_hexagon_buffer_type_interface = {
1671    /* .get_name         = */ ggml_backend_hexagon_buffer_type_name,
1672    /* .alloc_buffer     = */ ggml_backend_hexagon_buffer_type_alloc_buffer,
1673    /* .get_alignment    = */ ggml_backend_hexagon_buffer_type_get_alignment,
1674    /* .get_max_size     = */ ggml_backend_hexagon_buffer_type_get_max_size,
1675    /* .get_alloc_size   = */ ggml_backend_hexagon_buffer_type_get_alloc_size,
1676    /* .is_host          = */ ggml_backend_hexagon_buffer_type_is_host,
1677};
1678
1679static ggml_backend_buffer_type_i ggml_backend_hexagon_host_buffer_type_interface = {
1680    /* .get_name         = */ ggml_backend_hexagon_buffer_type_name,
1681    /* .alloc_buffer     = */ ggml_backend_hexagon_host_buffer_type_alloc_buffer,
1682    /* .get_alignment    = */ ggml_backend_hexagon_buffer_type_get_alignment,
1683    /* .get_max_size     = */ ggml_backend_hexagon_buffer_type_get_max_size,
1684    /* .get_alloc_size   = */ ggml_backend_hexagon_buffer_type_get_alloc_size,
1685    /* .is_host          = */ ggml_backend_hexagon_host_buffer_type_is_host,
1686};
1687
1688ggml_backend_hexagon_device_context::ggml_backend_hexagon_device_context(int dev_id, const ggml_hexagon_device_config & config, ggml_backend_dev_t dev)
1689    : dev_id(dev_id), config(config), dev(dev), max_bufsize(opt_mbuf) {
1690    buffer_type.device  = dev;
1691    buffer_type.iface   = ggml_backend_hexagon_buffer_type_interface;
1692    buffer_type.context = new ggml_backend_hexagon_buffer_type_context(config.name, this);
1693
1694    host_buffer_type.device  = dev;
1695    host_buffer_type.iface   = ggml_backend_hexagon_host_buffer_type_interface;
1696    host_buffer_type.context = new ggml_backend_hexagon_buffer_type_context(config.name + "-HOST", this);
1697
1698    fence_buffer_type.device  = dev;
1699    fence_buffer_type.iface   = ggml_backend_hexagon_buffer_type_interface;
1700    fence_buffer_type.context = new ggml_backend_hexagon_buffer_type_context(config.name + "-FENCE", this);
1701}
1702
1703ggml_backend_hexagon_device_context::~ggml_backend_hexagon_device_context() {
1704    delete static_cast<ggml_backend_hexagon_buffer_type_context *>(buffer_type.context);
1705    delete static_cast<ggml_backend_hexagon_buffer_type_context *>(host_buffer_type.context);
1706    delete static_cast<ggml_backend_hexagon_buffer_type_context *>(fence_buffer_type.context);
1707}
1708
1709static bool ggml_backend_buffer_is_hexagon(const struct ggml_backend_buffer * b) {
1710    return b->buft->iface.get_alignment == ggml_backend_hexagon_buffer_type_get_alignment;
1711}
1712
1713struct ggml_hexagon_opbatch {
1714    ggml_hexagon_session*            sess;
1715
1716    std::vector<htp_opnode>          ops;       // htp_opnode of ops
1717
1718    std::vector<htp_buf_desc>        h_bufs;    // htp buffer descriptors
1719    std::vector<htp_tensor>          h_tens;    // htp tensor descriptors
1720    std::vector<htp_op_desc>         h_ops;     // htp op descriptors
1721
1722    std::unordered_map<int, int>                b_map; // buffer fd   to index
1723    std::unordered_map<const ggml_tensor*, int> t_map; // tensor ptr  to index
1724    std::unordered_multimap<void*, int>         d_map; // tensor data to index
1725
1726    unsigned int n_bufs;     // num buffers in the batch
1727    unsigned int n_tens;     // num tensors ...
1728    unsigned int n_ops;      // num ops ...
1729    size_t       b_vmem;     // sum of all buffer sizes
1730
1731    unsigned int n_bufs_max;
1732    unsigned int n_tens_max;
1733    unsigned int n_ops_max;
1734    size_t       b_vmem_max;
1735
1736    void reset() {
1737        n_bufs = 0;
1738        n_tens = 0;
1739        n_ops  = 0;
1740        b_vmem = 0;
1741
1742        b_map.clear();
1743        t_map.clear();
1744        d_map.clear();
1745        ops.resize(n_ops_max);
1746    }
1747
1748    ggml_hexagon_opbatch(ggml_hexagon_session *sess, size_t batch_size, size_t max_vmem) {
1749        this->sess = sess;
1750
1751        n_bufs_max = HTP_OP_MAX_BUFS;
1752        n_ops_max  = batch_size;
1753        n_tens_max = std::min<size_t>(n_ops_max + n_ops_max * HTP_OP_MAX_INPUTS, HTP_OP_MAX_TENSORS);
1754
1755        b_vmem_max = max_vmem;
1756
1757        ops.resize(n_ops_max);
1758
1759        h_bufs.resize(n_bufs_max);
1760        h_tens.resize(n_tens_max);
1761        h_ops.resize(n_ops_max);
1762
1763        b_map.reserve(n_bufs_max);
1764        t_map.reserve(n_tens_max);
1765        d_map.reserve(n_tens_max);
1766
1767        GGML_LOG_INFO("ggml-hex: %s op batching: n-bufs %u n-tensors %u n-ops %u vmem %zu\n",
1768                sess->c_name(), n_bufs_max, n_tens_max, n_ops_max, b_vmem_max);
1769
1770        reset();
1771    }
1772
1773    bool empty() const { return n_ops == 0; }
1774
1775    // add buffer and return its index
1776    int add_buffer(ggml_hexagon_shared_buffer * sbuf) {
1777        // Lookup by fd
1778        auto it = b_map.find(sbuf->fd());
1779        if (it != b_map.end()) { return it->second; }
1780
1781        // Add new buffer to the batch
1782        GGML_ASSERT(n_bufs < HTP_OP_MAX_BUFS);
1783        int bi = n_bufs++;
1784
1785        b_map.insert({sbuf->fd(), bi});
1786
1787        htp_buf_desc &b = h_bufs[bi];
1788        b.base = (uint64_t) sbuf->base();
1789        b.fd   = sbuf->fd();
1790        b.size = sbuf->size();
1791
1792        b_vmem += b.size;
1793
1794        HEX_VERBOSE("ggml-hex: %s add-buffer #%u : fd %d base %p size %zu : vmem %zu\n", sess->c_name(), bi, b.fd, (void*) sbuf->base(), (size_t) b.size, b_vmem);
1795
1796        return bi;
1797    }
1798
1799    bool same_shape(const htp_tensor * h, const ggml_tensor * t) const {
1800        auto extra = (ggml_hexagon_tensor_extra *) t->extra;
1801
1802        int64_t ne0 = t->ne[0];
1803        int64_t ne1 = t->ne[1];
1804        const bool is_repack = (extra->flags & GGML_HEXAGON_TENSOR_REPACK) != 0;
1805        if (is_repack) {
1806            ne0 = hex_round_up(ne0, 32);
1807            ne1 = hex_round_up(ne1, 32);
1808        }
1809        int64_t nb1 = is_repack ? ggml_row_size(t->type, ne0) : t->nb[1];
1810        int64_t nb2 = is_repack ? nb1 * ne1      : t->nb[2];
1811        int64_t nb3 = is_repack ? nb2 * t->ne[2] : t->nb[3];
1812
1813        return (h->type == t->type) &&
1814               (h->ne[0] == ne0) && (h->ne[1] == ne1) && (h->ne[2] == t->ne[2]) && (h->ne[3] == t->ne[3]) &&
1815               (h->nb[0] == t->nb[0]) && (h->nb[1] == nb1) && (h->nb[2] == nb2) && (h->nb[3] == nb3);
1816    }
1817
1818    // add tensor and return its index
1819    int add_tensor(const ggml_tensor * t) {
1820        auto extra = (ggml_hexagon_tensor_extra *) t->extra;
1821        auto sbuf  = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
1822
1823        // First lookup by tensor data
1824        auto range = d_map.equal_range(t->data);
1825        for (auto it = range.first; it != range.second; ++it) {
1826            htp_tensor * h = &h_tens[it->second];
1827            if (same_shape(h, t)) { return it->second; }
1828        }
1829
1830        // Lookup by tensor ptr
1831        auto it = t_map.find(t);
1832        if (it != t_map.end()) { return it->second; }
1833
1834        // Add new tensor to the batch
1835        int ti = n_tens++;
1836        GGML_ASSERT(n_tens <= n_tens_max);
1837
1838        t_map.insert({t,       ti});
1839        d_map.insert({t->data, ti});
1840
1841        uint64_t t_offset = (uint8_t *) t->data - sbuf->base();
1842        size_t   t_size   = ggml_nbytes(t);
1843
1844        htp_tensor &h = h_tens[ti];
1845        h.bi    = add_buffer(sbuf);
1846        h.ti    = ti;
1847        h.data  = t_offset;
1848        h.type  = t->type;
1849
1850        const bool is_repack = (extra->flags & GGML_HEXAGON_TENSOR_REPACK) != 0;
1851        if (is_repack) {
1852            h.ne[0] = hex_round_up(t->ne[0], 32);
1853            h.ne[1] = hex_round_up(t->ne[1], 32);
1854            h.ne[2] = t->ne[2];
1855            h.ne[3] = t->ne[3];
1856
1857            h.nb[0] = t->nb[0];
1858            h.nb[1] = ggml_row_size(t->type, h.ne[0]);
1859            h.nb[2] = h.nb[1] * h.ne[1];
1860            h.nb[3] = h.nb[2] * h.ne[2];
1861            h.size  = h.nb[3] * h.ne[3];
1862            t_size  = h.size;
1863        } else {
1864            h.size  = t_size;
1865            h.ne[0] = t->ne[0]; h.ne[1] = t->ne[1]; h.ne[2] = t->ne[2]; h.ne[3] = t->ne[3];
1866            h.nb[0] = t->nb[0]; h.nb[1] = t->nb[1]; h.nb[2] = t->nb[2]; h.nb[3] = t->nb[3];
1867        }
1868
1869        h.flags = 0;
1870        if ((extra->flags & GGML_HEXAGON_TENSOR_WEIGHT) != 0) {
1871            h.flags |= HTP_TENSOR_WEIGHT;
1872        }
1873        if ((extra->flags & GGML_HEXAGON_TENSOR_REPACK) != 0) {
1874            h.flags |= HTP_TENSOR_REPACK;
1875        }
1876        if ((extra->flags & GGML_HEXAGON_TENSOR_FENCE) != 0) {
1877            h.flags |= HTP_TENSOR_FENCE;
1878        }
1879
1880        HEX_VERBOSE("ggml-hex: %s add-tensor #%u %s : bi %d data %p offset %zu size %zu flags 0x%x : %zu:%zu:%zu:%zu\n", sess->c_name(),
1881                ti, t->name, h.bi, (void*) t->data, (size_t) t_offset, t_size, h.flags,
1882                (size_t) h.ne[0], (size_t) h.ne[1], (size_t) h.ne[2], (size_t) h.ne[3]);
1883
1884        return ti;
1885    }
1886
1887    bool fit_op(const htp_opnode & node) const {
1888        if (n_ops >= n_ops_max ) return false;
1889
1890        // check how much extras we will need
1891        size_t extra_bufs = 0;
1892        size_t extra_vmem = 0;
1893        size_t extra_tens = 0;
1894
1895        auto fit_tensor = [&](const ggml_tensor *t) {
1896            if (!t) return;
1897            if (!t_map.count(t)) {
1898                extra_tens++;
1899
1900                auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
1901                if (!b_map.count(sbuf->fd())) {
1902                    extra_vmem += sbuf->size();
1903                    extra_bufs += 1;
1904                }
1905            }
1906        };
1907
1908        for (const auto * src : node.get_inputs()) {
1909            fit_tensor(src);
1910        }
1911        for (const auto * output : node.get_outputs()) {
1912            fit_tensor(output);
1913        }
1914
1915        if ((extra_bufs + n_bufs) > n_bufs_max) return false;
1916        if ((extra_tens + n_tens) > n_tens_max) return false;
1917        if ((extra_vmem + b_vmem) > b_vmem_max) return false;
1918
1919        return true;
1920    }
1921
1922    // assumes that fit_op() was called first and returned true
1923    void add_op(const htp_opnode & node) {
1924        // Add new op
1925
1926        unsigned int n = n_ops++;
1927        GGML_ASSERT(n_ops <= n_ops_max);
1928
1929        ops[n] = node;
1930
1931        htp_op_desc &o = h_ops[n];
1932        memcpy(o.params,        node.node->op_params, sizeof(node.node->op_params));
1933        memcpy(o.kernel_params, node.kernel_params,   sizeof(o.kernel_params));
1934        o.opcode = node.opcode;
1935        o.flags  = 0;
1936
1937        ggml_hexagon_dump_op_exec(sess->c_name(), ops[n], o.flags);
1938
1939        auto inputs = node.get_inputs();
1940        for (unsigned int i=0; i < HTP_OP_MAX_INPUTS; i++) {
1941            o.src[i] = (i < inputs.size() && inputs[i])   ? add_tensor(inputs[i]) : 0xffff;
1942        }
1943
1944        auto outputs = node.get_outputs();
1945        for (unsigned int i=0; i < HTP_OP_MAX_OUTPUTS; i++) {
1946            o.dst[i] = (i < outputs.size() && outputs[i]) ? add_tensor(outputs[i]) : 0xffff;
1947        }
1948    }
1949
1950    void sort_buffers() {
1951        if (n_bufs <= 1) return;
1952
1953        std::vector<int> order(n_bufs);
1954        for (unsigned int i = 0; i < n_bufs; i++) { order[i] = (int) i; }
1955
1956        std::stable_sort(order.begin(), order.end(), [&](int a, int b) {
1957            return h_bufs[a].size > h_bufs[b].size;
1958        });
1959
1960        bool already_sorted = true;
1961        for (unsigned int i = 0; i < n_bufs; i++) {
1962            if (order[i] != (int) i) {
1963                already_sorted = false;
1964                break;
1965            }
1966        }
1967        if (already_sorted) return;
1968
1969        std::vector<uint16_t> remap(n_bufs);
1970        std::vector<htp_buf_desc> sorted_bufs(n_bufs);
1971        for (unsigned int new_bi = 0; new_bi < n_bufs; new_bi++) {
1972            int old_bi = order[new_bi];
1973            remap[old_bi] = (uint16_t) new_bi;
1974            sorted_bufs[new_bi] = h_bufs[old_bi];
1975        }
1976
1977        for (unsigned int i = 0; i < n_bufs; i++) {
1978            h_bufs[i] = sorted_bufs[i];
1979        }
1980
1981        for (unsigned int i = 0; i < n_tens; i++) {
1982            h_tens[i].bi = remap[h_tens[i].bi];
1983        }
1984    }
1985
1986    void update_mdev_group(uint32_t mdev_idx) {
1987        if (n_ops > 0 && h_ops[0].opcode == HTP_OP_MDEV_GROUP) {
1988            h_ops[0].params[0] = (int32_t) mdev_idx;
1989        }
1990    }
1991
1992    bool try_fuse_allreduce_add(const htp_opnode & node) {
1993        if (n_ops == 0 || opt_ar_select != 2) return false;
1994        if (node.opcode != HTP_OP_ADD) return false;
1995
1996        htp_opnode & last_node = ops[n_ops - 1];
1997        if (last_node.opcode != HTP_OP_ALLREDUCE) return false;
1998
1999        auto * ar_kparams = (struct htp_allreduce_kernel_params *) last_node.kernel_params;
2000        const uint32_t rank    = (uint32_t) ar_kparams->rank;
2001        const uint32_t n_ranks = (uint32_t) ar_kparams->n_ranks;
2002        const ggml_tensor * ar_local = last_node.inputs[rank];
2003        const ggml_tensor * add_src0 = node.src0();
2004        const ggml_tensor * add_src1 = node.src1();
2005        const ggml_tensor * add_dst  = node.dst();
2006
2007        if (!ggml_hexagon_tensor_is_fuseable(ar_local)) return false;
2008
2009        const ggml_tensor * res_tensor;
2010        if (add_src0 == ar_local || add_src0->data == ar_local->data) {
2011            res_tensor = add_src1;
2012        } else if (add_src1 == ar_local || add_src1->data == ar_local->data) {
2013            res_tensor = add_src0;
2014        } else {
2015            return false;
2016        }
2017
2018        if (ar_local->type != res_tensor->type) return false;
2019
2020        const bool is_same_shape = (ar_local->ne[0] == res_tensor->ne[0] && ar_local->ne[1] == res_tensor->ne[1] &&
2021                                    ar_local->ne[2] == res_tensor->ne[2] && ar_local->ne[3] == res_tensor->ne[3]);
2022        const bool is_row_bcast  = !is_same_shape && (ar_local->ne[0] == res_tensor->ne[0] && res_tensor->ne[1] == 1 &&
2023                                                      res_tensor->ne[2] == 1 && res_tensor->ne[3] == 1);
2024
2025        if (!is_same_shape && !is_row_bcast) return false;
2026
2027        if (is_same_shape) {
2028            if (ar_local->nb[1] != res_tensor->nb[1] || ar_local->nb[2] != res_tensor->nb[2] ||
2029                ar_local->nb[3] != res_tensor->nb[3]) {
2030                return false;
2031            }
2032            if (ggml_is_contiguous(ar_local) != ggml_is_contiguous(res_tensor)) {
2033                return false;
2034            }
2035        }
2036        if (ggml_is_contiguous(ar_local) != ggml_is_contiguous(add_dst)) {
2037            return false;
2038        }
2039
2040        for (uint32_t r = 0; r < n_ranks; r++) {
2041            const ggml_tensor * ar_src = last_node.inputs[r];
2042            if (ggml_hexagon_tensors_overlap(add_dst, ar_src)) {
2043                HEX_VERBOSE("ggml-hex: %s skip ALLREDUCE_ADD fusion: dst overlaps allreduce src %u\n", sess->c_name(), r);
2044                return false;
2045            }
2046        }
2047
2048        struct htp_allreduce_kernel_params new_kparams;
2049        if (!ggml_hexagon_precompute_allreduce_params(
2050            sess, add_dst, (uint32_t) ar_kparams->rank, (uint32_t) ar_kparams->n_ranks, true, is_row_bcast, &new_kparams
2051        )) {
2052            HEX_VERBOSE("ggml-hex: %s skip ALLREDUCE_ADD fusion: solver failed\n", sess->c_name());
2053            return false;
2054        }
2055
2056        size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2057        auto fit_t = [&](const ggml_tensor * t) {
2058            if (!t_map.count(t)) {
2059                extra_tens++;
2060                auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2061                if (!b_map.count(sbuf->fd())) {
2062                    extra_vmem += sbuf->size();
2063                    extra_bufs += 1;
2064                }
2065            }
2066        };
2067        fit_t(res_tensor);
2068        fit_t(add_dst);
2069        if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2070            return false;
2071        }
2072
2073        last_node.opcode = HTP_OP_ALLREDUCE_ADD;
2074        last_node.name   = "ALLREDUCE+ADD";
2075        last_node.inputs.push_back(res_tensor);
2076        last_node.outputs.clear();
2077        last_node.outputs.push_back(add_dst);
2078        last_node.fused.push_back(node.node);
2079        memcpy(last_node.kernel_params, &new_kparams, sizeof(new_kparams));
2080
2081        htp_op_desc & o = h_ops[n_ops - 1];
2082        o.opcode = HTP_OP_ALLREDUCE_ADD;
2083        memcpy(o.kernel_params, &new_kparams, sizeof(new_kparams));
2084
2085        o.src[2 * n_ranks] = add_tensor(res_tensor);
2086        o.dst[0]           = add_tensor(add_dst);
2087        for (uint32_t d = 1; d < HTP_OP_MAX_OUTPUTS; d++) {
2088            o.dst[d] = 0xffff;
2089        }
2090
2091        HEX_VERBOSE("ggml-hex: %s fused ALLREDUCE+ADD (#%u)\n", sess->c_name(), n_ops - 1);
2092        return true;
2093    }
2094
2095    bool try_fuse_rms_norm_mul(const htp_opnode & node) {
2096        if (n_ops == 0) return false;
2097        if (node.opcode != HTP_OP_MUL) return false;
2098
2099        htp_opnode & last_node = ops[n_ops - 1];
2100        if (last_node.opcode != HTP_OP_RMS_NORM) return false;
2101
2102        const ggml_tensor * mul_src0 = node.src0();
2103        const ggml_tensor * mul_src1 = node.src1();
2104        const ggml_tensor * rms_out  = last_node.dst();
2105
2106        if (!ggml_hexagon_tensor_is_fuseable(rms_out)) return false;
2107
2108        const ggml_tensor * weight;
2109        if (mul_src0 == rms_out || mul_src0->data == rms_out->data) {
2110            weight = mul_src1;
2111        } else if (mul_src1 == rms_out || mul_src1->data == rms_out->data) {
2112            weight = mul_src0;
2113        } else {
2114            return false;
2115        }
2116
2117        const ggml_tensor * src0 = last_node.src0();
2118
2119        if (src0->ne[0] != weight->ne[0] || src0->ne[0] != node.dst()->ne[0]) {
2120            return false;
2121        }
2122
2123        const bool is_row_bcast = (weight->ne[1] == 1 && weight->ne[2] == 1 && weight->ne[3] == 1);
2124        const bool is_same_shape = (src0->ne[0] == weight->ne[0] && src0->ne[1] == weight->ne[1] &&
2125                                    src0->ne[2] == weight->ne[2] && src0->ne[3] == weight->ne[3]);
2126        if (!is_row_bcast && !is_same_shape) return false;
2127
2128        if (!ggml_are_same_shape(src0, node.dst())) {
2129            return false;
2130        }
2131        if (ggml_is_contiguous(src0) != ggml_is_contiguous(node.dst())) {
2132            return false;
2133        }
2134
2135        struct htp_unary_kernel_params new_kparams;
2136        ggml_hexagon_precompute_unary_params(
2137            sess, HTP_OP_RMS_NORM_MUL, src0, weight, node.dst(), &new_kparams
2138        );
2139
2140        if ((size_t) new_kparams.vtcm_size > sess->vtcm_size) {
2141            HEX_VERBOSE("ggml-hex: %s skip RMS_NORM_MUL fusion: VTCM needed (%d) > budget (%zu)\n",
2142                        sess->c_name(), new_kparams.vtcm_size, sess->vtcm_size);
2143            return false;
2144        }
2145
2146        size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2147        auto fit_t = [&](const ggml_tensor * t) {
2148            if (!t_map.count(t)) {
2149                extra_tens++;
2150                auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2151                if (!b_map.count(sbuf->fd())) {
2152                    extra_vmem += sbuf->size();
2153                    extra_bufs += 1;
2154                }
2155            }
2156        };
2157        fit_t(weight);
2158        fit_t(node.dst());
2159        if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2160            return false;
2161        }
2162
2163        last_node.opcode = HTP_OP_RMS_NORM_MUL;
2164        last_node.name   = "RMS_NORM+MUL";
2165        last_node.inputs.clear();
2166        last_node.inputs.push_back(src0);
2167        last_node.inputs.push_back(weight);
2168        last_node.outputs.clear();
2169        last_node.outputs.push_back(node.dst());
2170        last_node.fused.push_back(node.node);
2171        memcpy(last_node.kernel_params, &new_kparams, sizeof(new_kparams));
2172
2173        htp_op_desc & o = h_ops[n_ops - 1];
2174        o.opcode = HTP_OP_RMS_NORM_MUL;
2175        memcpy(o.kernel_params, &new_kparams, sizeof(new_kparams));
2176
2177        o.src[0] = add_tensor(src0);
2178        o.src[1] = add_tensor(weight);
2179        for (uint32_t s = 2; s < HTP_OP_MAX_INPUTS; s++) {
2180            o.src[s] = 0xffff;
2181        }
2182        o.dst[0] = add_tensor(node.dst());
2183        for (uint32_t d = 1; d < HTP_OP_MAX_OUTPUTS; d++) {
2184            o.dst[d] = 0xffff;
2185        }
2186
2187        HEX_VERBOSE("ggml-hex: %s fused RMS_NORM+MUL (#%u)\n", sess->c_name(), n_ops - 1);
2188        return true;
2189    }
2190
2191    bool try_fuse_mul_mat_add(const htp_opnode & node) {
2192        if (n_ops == 0) return false;
2193        if (node.opcode != HTP_OP_ADD) return false;
2194
2195        htp_opnode & last_node = ops[n_ops - 1];
2196        if (last_node.opcode != HTP_OP_MUL_MAT) return false;
2197
2198        const ggml_tensor * add_src0 = node.src0();
2199        const ggml_tensor * add_src1 = node.src1();
2200        const ggml_tensor * mm_out   = last_node.dst();
2201
2202        if (!ggml_hexagon_tensor_is_fuseable(mm_out)) return false;
2203
2204        const ggml_tensor * src2;
2205        if (add_src0 == mm_out || add_src0->data == mm_out->data) {
2206            src2 = add_src1;
2207        } else if (add_src1 == mm_out || add_src1->data == mm_out->data) {
2208            src2 = add_src0;
2209        } else {
2210            return false;
2211        }
2212
2213        const ggml_tensor * src0 = last_node.src0();
2214        const ggml_tensor * src1 = last_node.src1();
2215
2216        struct htp_mm_kernel_params kparams;
2217        ggml_hexagon_precompute_fused_matmul_add_params(sess, src0, src1, src2, node.dst(), &kparams);
2218        const int src1_nrows = src1->ne[1] * src1->ne[2] * src1->ne[3];
2219        const bool can_fuse = (kparams.n_hmx > 0) || (src1_nrows == 1);
2220        if (!can_fuse) return false;
2221
2222        if ((size_t) kparams.vtcm_size > sess->vtcm_size) {
2223            HEX_VERBOSE("ggml-hex: %s skip MUL_MAT_ADD fusion: VTCM needed (%d) > budget (%zu)\n",
2224                        sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
2225            return false;
2226        }
2227
2228        size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2229        auto fit_t = [&](const ggml_tensor * t) {
2230            if (!t_map.count(t)) {
2231                extra_tens++;
2232                auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2233                if (!b_map.count(sbuf->fd())) {
2234                    extra_vmem += sbuf->size();
2235                    extra_bufs += 1;
2236                }
2237            }
2238        };
2239        fit_t(src2);
2240        fit_t(node.dst());
2241        if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2242            return false;
2243        }
2244
2245        last_node.opcode = HTP_OP_MUL_MAT_ADD;
2246        last_node.name   = "MUL_MAT+ADD";
2247        last_node.inputs.clear();
2248        last_node.inputs.push_back(src0);
2249        last_node.inputs.push_back(src1);
2250        last_node.inputs.push_back(src2);
2251        last_node.outputs.clear();
2252        last_node.outputs.push_back(node.dst());
2253        last_node.fused.push_back(node.node);
2254        memcpy(last_node.kernel_params, &kparams, sizeof(kparams));
2255
2256        htp_op_desc & o = h_ops[n_ops - 1];
2257        o.opcode = HTP_OP_MUL_MAT_ADD;
2258        memcpy(o.kernel_params, &kparams, sizeof(kparams));
2259
2260        o.src[0] = add_tensor(src0);
2261        o.src[1] = add_tensor(src1);
2262        o.src[2] = add_tensor(src2);
2263        for (uint32_t s = 3; s < HTP_OP_MAX_INPUTS; s++) {
2264            o.src[s] = 0xffff;
2265        }
2266        o.dst[0] = add_tensor(node.dst());
2267        for (uint32_t d = 1; d < HTP_OP_MAX_OUTPUTS; d++) {
2268            o.dst[d] = 0xffff;
2269        }
2270
2271        HEX_VERBOSE("ggml-hex: %s fused MUL_MAT+ADD (#%u)\n", sess->c_name(), n_ops - 1);
2272        return true;
2273    }
2274
2275    bool try_fuse_mul_mat_nx(const htp_opnode & node) {
2276        if (n_ops == 0 || node.opcode != HTP_OP_MUL_MAT) return false;
2277        if (!is_mergeable_mul_mat(node.node)) return false;
2278
2279        const ggml_tensor * w_in = node.src0();
2280        const ggml_tensor * x_in = node.src1();
2281        const ggml_tensor * d_in = node.dst();
2282
2283        htp_opnode & last_node = ops[n_ops - 1];
2284
2285        // Case 1: last_node is already MUL_MAT_NX
2286        if (last_node.opcode == HTP_OP_MUL_MAT_NX) {
2287            const uint32_t curr_n = (uint32_t) last_node.outputs.size();
2288            if (curr_n >= HTP_OP_MAX_OUTPUTS || curr_n + 1 >= HTP_OP_MAX_INPUTS) {
2289                return false;
2290            }
2291
2292            const ggml_tensor * w0 = last_node.inputs[0];
2293            const ggml_tensor * x  = last_node.inputs[curr_n];
2294
2295            if (x_in != x || w_in->type != w0->type || w_in->ne[0] != w0->ne[0]) {
2296                return false;
2297            }
2298            if (!last_node.fused.empty() && (mm_is_hmx_eligible(last_node.fused[0]) != mm_is_hmx_eligible(node.node))) {
2299                return false;
2300            }
2301
2302            struct htp_mm_kernel_params kparams;
2303            ggml_hexagon_precompute_fused_mmnx_params(sess, w0, x, curr_n + 1, &kparams);
2304            if (!is_supported_mul_mat_nx_kernel(w0, &kparams)) {
2305                return false;
2306            }
2307            if ((size_t) kparams.vtcm_size > sess->vtcm_size) {
2308                HEX_VERBOSE("ggml-hex: %s skip NX fusion: VTCM needed (%d) > budget (%zu)\n",
2309                            sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
2310                return false;
2311            }
2312
2313            size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2314            auto fit_t = [&](const ggml_tensor * t) {
2315                if (!t_map.count(t)) {
2316                    extra_tens++;
2317                    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2318                    if (!b_map.count(sbuf->fd())) {
2319                        extra_vmem += sbuf->size();
2320                        extra_bufs += 1;
2321                    }
2322                }
2323            };
2324            fit_t(w_in);
2325            fit_t(d_in);
2326            if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2327                return false;
2328            }
2329
2330            last_node.inputs[curr_n] = w_in;
2331            last_node.inputs.push_back(x);
2332            last_node.outputs.push_back(d_in);
2333            last_node.fused.push_back(node.node);
2334            memcpy(last_node.kernel_params, &kparams, sizeof(kparams));
2335
2336            htp_op_desc & o = h_ops[n_ops - 1];
2337            memcpy(o.kernel_params, &kparams, sizeof(kparams));
2338
2339            for (uint32_t s = 0; s <= curr_n + 1; s++) {
2340                o.src[s] = add_tensor(last_node.inputs[s]);
2341            }
2342            for (uint32_t s = curr_n + 2; s < HTP_OP_MAX_INPUTS; s++) {
2343                o.src[s] = 0xffff;
2344            }
2345            for (uint32_t d = 0; d <= curr_n; d++) {
2346                o.dst[d] = add_tensor(last_node.outputs[d]);
2347            }
2348            for (uint32_t d = curr_n + 1; d < HTP_OP_MAX_OUTPUTS; d++) {
2349                o.dst[d] = 0xffff;
2350            }
2351
2352            HEX_VERBOSE("ggml-hex: %s fused MUL_MAT_NX (N=%u, #%u)\n", sess->c_name(), curr_n + 1, n_ops - 1);
2353            return true;
2354        }
2355
2356        // Case 2: last_node is single MUL_MAT
2357        if (last_node.opcode == HTP_OP_MUL_MAT) {
2358            if (!is_mergeable_mul_mat_pair(last_node.node, node.node)) {
2359                return false;
2360            }
2361
2362            const ggml_tensor * w0 = last_node.src0();
2363            const ggml_tensor * x  = last_node.src1();
2364            const ggml_tensor * w1 = node.src0();
2365
2366            struct htp_mm_kernel_params kparams;
2367            ggml_hexagon_precompute_fused_mmnx_params(sess, w0, x, 2, &kparams);
2368            if (!is_supported_mul_mat_nx_kernel(w0, &kparams)) {
2369                return false;
2370            }
2371            if ((size_t) kparams.vtcm_size > sess->vtcm_size) {
2372                HEX_VERBOSE("ggml-hex: %s skip NX fusion: VTCM needed (%d) > budget (%zu)\n",
2373                            sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
2374                return false;
2375            }
2376
2377            size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2378            auto fit_t = [&](const ggml_tensor * t) {
2379                if (!t_map.count(t)) {
2380                    extra_tens++;
2381                    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2382                    if (!b_map.count(sbuf->fd())) {
2383                        extra_vmem += sbuf->size();
2384                        extra_bufs += 1;
2385                    }
2386                }
2387            };
2388            fit_t(w1);
2389            fit_t(node.dst());
2390            if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2391                return false;
2392            }
2393
2394            const ggml_tensor * dst_0 = last_node.dst();
2395            const ggml_tensor * dst_1 = node.dst();
2396
2397            last_node.opcode = HTP_OP_MUL_MAT_NX;
2398            last_node.name   = "MUL_MAT_NX";
2399            last_node.inputs.clear();
2400            last_node.inputs.push_back(w0);
2401            last_node.inputs.push_back(w1);
2402            last_node.inputs.push_back(x);
2403            last_node.outputs.clear();
2404            last_node.outputs.push_back(dst_0);
2405            last_node.outputs.push_back(dst_1);
2406            last_node.fused.push_back(node.node);
2407            memcpy(last_node.kernel_params, &kparams, sizeof(kparams));
2408
2409            htp_op_desc & o = h_ops[n_ops - 1];
2410            o.opcode = HTP_OP_MUL_MAT_NX;
2411            memcpy(o.kernel_params, &kparams, sizeof(kparams));
2412
2413            o.src[0] = add_tensor(w0);
2414            o.src[1] = add_tensor(w1);
2415            o.src[2] = add_tensor(x);
2416            for (uint32_t s = 3; s < HTP_OP_MAX_INPUTS; s++) {
2417                o.src[s] = 0xffff;
2418            }
2419            o.dst[0] = add_tensor(dst_0);
2420            o.dst[1] = add_tensor(dst_1);
2421            for (uint32_t d = 2; d < HTP_OP_MAX_OUTPUTS; d++) {
2422                o.dst[d] = 0xffff;
2423            }
2424
2425            HEX_VERBOSE("ggml-hex: %s fused MUL_MAT_NX (N=2, #%u)\n", sess->c_name(), n_ops - 1);
2426            return true;
2427        }
2428
2429        return false;
2430    }
2431
2432    bool try_fuse_mul_mat_id_nx(const htp_opnode & node) {
2433        if (n_ops == 0 || node.opcode != HTP_OP_MUL_MAT_ID) return false;
2434        if (!is_mergeable_mul_mat_id(node.node)) return false;
2435
2436        const ggml_tensor * w_in   = node.src0();
2437        const ggml_tensor * x_in   = node.src1();
2438        const ggml_tensor * ids_in = node.node->src[2];
2439        const ggml_tensor * d_in   = node.dst();
2440
2441        htp_opnode & last_node = ops[n_ops - 1];
2442
2443        // Case 1: last_node is already MUL_MAT_ID_NX
2444        if (last_node.opcode == HTP_OP_MUL_MAT_ID_NX) {
2445            const uint32_t curr_n = (uint32_t) last_node.outputs.size();
2446            if (curr_n >= HTP_OP_MAX_OUTPUTS || curr_n + 2 >= HTP_OP_MAX_INPUTS) {
2447                return false;
2448            }
2449
2450            const ggml_tensor * w0  = last_node.inputs[0];
2451            const ggml_tensor * x   = last_node.inputs[curr_n];
2452            const ggml_tensor * ids = last_node.inputs[curr_n + 1];
2453
2454            if (x_in != x || ids_in != ids || w_in->type != w0->type || w_in->ne[0] != w0->ne[0] || w_in->ne[2] != w0->ne[2]) {
2455                return false;
2456            }
2457            if (!last_node.fused.empty() && (mm_is_hmx_eligible(last_node.fused[0]) != mm_is_hmx_eligible(node.node))) {
2458                return false;
2459            }
2460
2461            struct htp_mm_kernel_params kparams;
2462            ggml_hexagon_precompute_fused_mmidnx_params(sess, w0, x, d_in, curr_n + 1, &kparams);
2463            if (!is_supported_mul_mat_id_nx_kernel(w0, &kparams)) {
2464                return false;
2465            }
2466            if ((size_t) kparams.vtcm_size > sess->vtcm_size) {
2467                HEX_VERBOSE("ggml-hex: %s skip ID NX fusion: VTCM needed (%d) > budget (%zu)\n",
2468                            sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
2469                return false;
2470            }
2471
2472            size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2473            auto fit_t = [&](const ggml_tensor * t) {
2474                if (!t_map.count(t)) {
2475                    extra_tens++;
2476                    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2477                    if (!b_map.count(sbuf->fd())) {
2478                        extra_vmem += sbuf->size();
2479                        extra_bufs += 1;
2480                    }
2481                }
2482            };
2483            fit_t(w_in);
2484            fit_t(d_in);
2485            if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2486                return false;
2487            }
2488
2489            last_node.inputs[curr_n] = w_in;
2490            last_node.inputs[curr_n + 1] = x;
2491            last_node.inputs.push_back(ids);
2492            last_node.outputs.push_back(d_in);
2493            last_node.fused.push_back(node.node);
2494            memcpy(last_node.kernel_params, &kparams, sizeof(kparams));
2495
2496            htp_op_desc & o = h_ops[n_ops - 1];
2497            memcpy(o.kernel_params, &kparams, sizeof(kparams));
2498
2499            for (uint32_t s = 0; s <= curr_n + 2; s++) {
2500                o.src[s] = add_tensor(last_node.inputs[s]);
2501            }
2502            for (uint32_t s = curr_n + 3; s < HTP_OP_MAX_INPUTS; s++) {
2503                o.src[s] = 0xffff;
2504            }
2505            for (uint32_t d = 0; d <= curr_n; d++) {
2506                o.dst[d] = add_tensor(last_node.outputs[d]);
2507            }
2508            for (uint32_t d = curr_n + 1; d < HTP_OP_MAX_OUTPUTS; d++) {
2509                o.dst[d] = 0xffff;
2510            }
2511
2512            HEX_VERBOSE("ggml-hex: %s fused MUL_MAT_ID_NX (N=%u, #%u)\n", sess->c_name(), curr_n + 1, n_ops - 1);
2513            return true;
2514        }
2515
2516        // Case 2: last_node is single MUL_MAT_ID
2517        if (last_node.opcode == HTP_OP_MUL_MAT_ID) {
2518            if (!is_mergeable_mul_mat_id_pair(last_node.node, node.node)) {
2519                return false;
2520            }
2521
2522            const ggml_tensor * w0  = last_node.src0();
2523            const ggml_tensor * x   = last_node.src1();
2524            const ggml_tensor * ids = last_node.node->src[2];
2525            const ggml_tensor * w1  = node.src0();
2526
2527            struct htp_mm_kernel_params kparams;
2528            ggml_hexagon_precompute_fused_mmidnx_params(sess, w0, x, node.dst(), 2, &kparams);
2529            if (!is_supported_mul_mat_id_nx_kernel(w0, &kparams)) {
2530                return false;
2531            }
2532            if ((size_t) kparams.vtcm_size > sess->vtcm_size) {
2533                HEX_VERBOSE("ggml-hex: %s skip ID NX fusion: VTCM needed (%d) > budget (%zu)\n",
2534                            sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
2535                return false;
2536            }
2537
2538            size_t extra_bufs = 0, extra_vmem = 0, extra_tens = 0;
2539            auto fit_t = [&](const ggml_tensor * t) {
2540                if (!t_map.count(t)) {
2541                    extra_tens++;
2542                    auto sbuf = static_cast<ggml_hexagon_shared_buffer *>(t->buffer->context);
2543                    if (!b_map.count(sbuf->fd())) {
2544                        extra_vmem += sbuf->size();
2545                        extra_bufs += 1;
2546                    }
2547                }
2548            };
2549            fit_t(w1);
2550            fit_t(node.dst());
2551            if ((extra_bufs + n_bufs) > n_bufs_max || (extra_tens + n_tens) > n_tens_max || (extra_vmem + b_vmem) > b_vmem_max) {
2552                return false;
2553            }
2554
2555            const ggml_tensor * dst_0 = last_node.dst();
2556            const ggml_tensor * dst_1 = node.dst();
2557
2558            last_node.opcode = HTP_OP_MUL_MAT_ID_NX;
2559            last_node.name   = "MUL_MAT_ID_NX";
2560            last_node.inputs.clear();
2561            last_node.inputs.push_back(w0);
2562            last_node.inputs.push_back(w1);
2563            last_node.inputs.push_back(x);
2564            last_node.inputs.push_back(ids);
2565            last_node.outputs.clear();
2566            last_node.outputs.push_back(dst_0);
2567            last_node.outputs.push_back(dst_1);
2568            last_node.fused.push_back(node.node);
2569            memcpy(last_node.kernel_params, &kparams, sizeof(kparams));
2570
2571            htp_op_desc & o = h_ops[n_ops - 1];
2572            o.opcode = HTP_OP_MUL_MAT_ID_NX;
2573            memcpy(o.kernel_params, &kparams, sizeof(kparams));
2574
2575            o.src[0] = add_tensor(w0);
2576            o.src[1] = add_tensor(w1);
2577            o.src[2] = add_tensor(x);
2578            o.src[3] = add_tensor(ids);
2579            for (uint32_t s = 4; s < HTP_OP_MAX_INPUTS; s++) {
2580                o.src[s] = 0xffff;
2581            }
2582            o.dst[0] = add_tensor(dst_0);
2583            o.dst[1] = add_tensor(dst_1);
2584            for (uint32_t d = 2; d < HTP_OP_MAX_OUTPUTS; d++) {
2585                o.dst[d] = 0xffff;
2586            }
2587
2588            HEX_VERBOSE("ggml-hex: %s fused MUL_MAT_ID_NX (N=2, #%u)\n", sess->c_name(), n_ops - 1);
2589            return true;
2590        }
2591
2592        return false;
2593    }
2594
2595    bool try_fuse(const htp_opnode & node) {
2596        if (!opt_opfusion) return false;
2597        if (ggml_hexagon_is_fusion_enabled(GGML_HEXAGON_FUSE_ALLREDUCE_ADD) && try_fuse_allreduce_add(node)) return true;
2598        if (ggml_hexagon_is_fusion_enabled(GGML_HEXAGON_FUSE_RMS_NORM_MUL)  && try_fuse_rms_norm_mul(node))  return true;
2599        if (ggml_hexagon_is_fusion_enabled(GGML_HEXAGON_FUSE_MUL_MAT_ADD)   && try_fuse_mul_mat_add(node))   return true;
2600        if (ggml_hexagon_is_fusion_enabled(GGML_HEXAGON_FUSE_MUL_MAT_NX)    && try_fuse_mul_mat_nx(node))    return true;
2601        if (ggml_hexagon_is_fusion_enabled(GGML_HEXAGON_FUSE_MUL_MAT_ID_NX) && try_fuse_mul_mat_id_nx(node)) return true;
2602        return false;
2603    }
2604};
2605
2606struct ggml_hexagon_registry {
2607    ggml_hexagon_registry(ggml_backend_reg_t reg);
2608    ~ggml_hexagon_registry();
2609
2610    ggml_backend_device devices[GGML_HEXAGON_MAX_SESSIONS];
2611};
2612
2613struct ggml_hexagon_opqueue {
2614    // Shared buffer for storing batches
2615    ggml_hexagon_shared_buffer *shm_buf;
2616    size_t                      shm_blk_size;
2617    size_t                      depth;
2618
2619    using opvec = std::vector<htp_opnode>;
2620
2621    std::vector<opvec>          op_cache;       // per batch op cache
2622    std::vector<uint64_t>       start_usec;     // per batch start time
2623
2624    ggml_hexagon_opqueue(ggml_hexagon_session *sess, size_t batch_size, size_t depth) : depth(depth) {
2625        size_t n_bufs    = HTP_OP_MAX_BUFS;
2626        size_t n_ops     = batch_size;
2627        size_t n_tensors = n_ops * HTP_OP_MAX_OUTPUTS + n_ops * HTP_OP_MAX_INPUTS;
2628
2629        size_t tr_size = 0;
2630        if (opt_profile == 3) {
2631            tr_size = (HTP_MAX_NTHREADS + 1) * opt_optrace * sizeof(htp_trace_desc);
2632        }
2633
2634        shm_blk_size = sizeof(htp_buf_desc)  * n_bufs    +
2635                       sizeof(htp_tensor)    * n_tensors +
2636                       sizeof(htp_op_desc)   * n_ops     +
2637                       sizeof(htp_prof_desc) * n_ops     +
2638                       tr_size;
2639
2640        shm_buf = new ggml_hexagon_shared_buffer(sess, shm_blk_size * depth, true /* pinned */);
2641
2642        op_cache.resize(depth);
2643        start_usec.resize(depth, 0);
2644
2645        if (opt_verbose) {
2646            GGML_LOG_INFO("ggml-hex: %s allocated opqueue : batch-size %zu depth %zu shm-size %zu shm-block-size %zu\n",
2647                    sess->c_name(), batch_size, depth, shm_buf->size(), shm_blk_size);
2648        }
2649    }
2650
2651    ~ggml_hexagon_opqueue() {
2652        delete shm_buf;
2653    }
2654
2655    size_t shm_size() const { return shm_buf ? shm_buf->size() : 0; }
2656
2657    // push new batch
2658    bool push(htp_opbatch_req& req, dspqueue_buffer& dbuf, const ggml_hexagon_opbatch* op_batch, uint64_t seq) {
2659        static_assert(sizeof(htp_opbatch_req) % 8 == 0, "sizeof(htp_opbatch_req) must be multiple of 8");
2660        static_assert(sizeof(htp_opbatch_rsp) % 8 == 0, "sizeof(htp_opbatch_rsp) must be multiple of 8");
2661        static_assert(sizeof(htp_buf_desc)    % 8 == 0, "sizeof(htp_buf_desc) must be multiple of 8");
2662        static_assert(sizeof(htp_tensor)      % 8 == 0, "sizeof(htp_tensor) must be multiple of 8");
2663        static_assert(sizeof(htp_op_desc)     % 8 == 0, "sizeof(htp_op_desc) must be multiple of 8");
2664        static_assert(sizeof(htp_prof_desc)   % 8 == 0, "sizeof(htp_prof_desc) must be multiple of 8");
2665
2666        if (seq - shm_buf->sess->batch_rsp_seq > depth) { return false; }
2667
2668        const uint32_t slot = (uint32_t) ((seq - 1) % depth);
2669
2670        req.seq       = seq;
2671        req.n_bufs    = op_batch->n_bufs;
2672        req.n_tensors = op_batch->n_tens;
2673        req.n_ops     = op_batch->n_ops;
2674
2675        op_cache[slot]   = op_batch->ops;
2676        start_usec[slot] = ggml_time_us();
2677
2678        const size_t b_size = sizeof(htp_buf_desc)  * req.n_bufs;
2679        const size_t t_size = sizeof(htp_tensor)    * req.n_tensors;
2680        const size_t o_size = sizeof(htp_op_desc)   * req.n_ops;
2681        const size_t p_size = sizeof(htp_prof_desc) * req.n_ops;
2682
2683        size_t tr_size = 0;
2684        if (opt_profile == 3) {
2685            req.n_traces = opt_optrace;
2686            tr_size = (HTP_MAX_NTHREADS + 1) * req.n_traces * sizeof(htp_trace_desc);
2687        } else {
2688            req.n_traces = 0;
2689        }
2690
2691        dbuf.ptr      = shm_buf->base() + ((size_t) slot * shm_blk_size);
2692        dbuf.fd       = shm_buf->fd();
2693        dbuf.flags    = DSPQUEUE_BUFFER_FLAG_FLUSH_SENDER | DSPQUEUE_BUFFER_FLAG_INVALIDATE_RECIPIENT;
2694        dbuf.offset   = (uint8_t*) dbuf.ptr - (uint8_t*) shm_buf->base();
2695        dbuf.size     = b_size + t_size + o_size + p_size + tr_size;
2696
2697        GGML_ASSERT(dbuf.size <= shm_blk_size);
2698
2699        uint8_t * m_ptr = (uint8_t*) dbuf.ptr;
2700        uint8_t * b_ptr = m_ptr; m_ptr += b_size;
2701        uint8_t * t_ptr = m_ptr; m_ptr += t_size;
2702        uint8_t * o_ptr = m_ptr;
2703
2704        memcpy(b_ptr, (void *) op_batch->h_bufs.data(), b_size);
2705        memcpy(t_ptr, (void *) op_batch->h_tens.data(), t_size);
2706        memcpy(o_ptr, (void *) op_batch->h_ops.data(),  o_size);
2707
2708        HEX_VERBOSE("ggml-hex: %s opqueue-push batch #%llu : n-bufs %u n-tensors %u n-ops %u vmem %zu : b-size %zu t-size %zu o-size %zu m-size %zu\n",
2709                shm_buf->sess->c_name(), (unsigned long long) req.seq, req.n_bufs, req.n_tensors, req.n_ops, op_batch->b_vmem,
2710                b_size, t_size, o_size, (size_t) dbuf.size);
2711
2712        if (opt_verbose > 1) {
2713            htp_buf_desc *b = (htp_buf_desc*) b_ptr;
2714            for (unsigned int i=0; i < req.n_bufs; i++) {
2715                GGML_LOG_DEBUG("ggml-hex: %s htp-buf #%u : fd %d base %p size %zu\n", shm_buf->sess->c_name(), i,
2716                            b[i].fd, (void *) b[i].base, (size_t) b[i].size);
2717            }
2718            htp_tensor *t = (htp_tensor*) t_ptr;
2719            for (unsigned int i=0; i < req.n_tensors; i++) {
2720                GGML_LOG_DEBUG("ggml-hex: %s htp-tensor #%u : bi %u offset %u size %u : %zu:%zu:%zu:%zu\n",
2721                            shm_buf->sess->c_name(), i, t[i].bi, t[i].data, t[i].size,
2722                            (size_t) t[i].ne[0], (size_t) t[i].ne[1], (size_t) t[i].ne[2], (size_t) t[i].ne[3]);
2723            }
2724        }
2725
2726        return true;
2727    }
2728
2729    void pop(htp_opbatch_rsp rsp, dspqueue_buffer dbuf) {
2730        const uint32_t slot = (uint32_t) ((rsp.seq - 1) % depth);
2731
2732        const size_t b_size = sizeof(htp_buf_desc)  * rsp.n_bufs;
2733        const size_t t_size = sizeof(htp_tensor)    * rsp.n_tensors;
2734        const size_t o_size = sizeof(htp_op_desc)   * rsp.n_ops;
2735        const size_t p_size = sizeof(htp_prof_desc) * rsp.n_ops;
2736
2737        size_t tr_size = 0;
2738        uint32_t n_traces = 0;
2739        if (opt_profile == 3) {
2740            n_traces = opt_optrace;
2741            tr_size = (HTP_MAX_NTHREADS + 1) * n_traces * sizeof(htp_trace_desc);
2742        }
2743
2744        const size_t m_size = b_size + t_size + o_size + p_size + tr_size;
2745        GGML_ASSERT(m_size <= shm_blk_size);
2746
2747        HEX_VERBOSE("ggml-hex: %s opqueue-pop batch #%llu : n-bufs %u n-tensors %u n-ops %u : m-size %zu b-size %zu t-size %zu o-size %zu\n",
2748                shm_buf->sess->c_name(), (unsigned long long) rsp.seq, rsp.n_bufs, rsp.n_tensors, rsp.n_ops,
2749                (size_t) dbuf.size, b_size, t_size, o_size);
2750
2751        uint8_t * m_ptr = (uint8_t*) dbuf.ptr;
2752        uint8_t * p_ptr = m_ptr + (b_size + t_size + o_size);
2753
2754        if (rsp.n_ops > 0) {
2755            auto & ops = op_cache[slot];
2756            GGML_ASSERT(rsp.n_ops <= ops.size());
2757
2758            const htp_prof_desc * pd = (const htp_prof_desc *) p_ptr;
2759            const htp_trace_desc * trace_events = nullptr;
2760            if (opt_profile == 3) {
2761                trace_events = (const htp_trace_desc *) (p_ptr + p_size);
2762            }
2763
2764            if (opt_profile) {
2765                ggml_hexagon_dump_batch_prof(shm_buf->sess->name, rsp);
2766            }
2767
2768            for (uint32_t i = 0; i < rsp.n_ops; i++) {
2769                if (opt_profile) {
2770                    ggml_hexagon_dump_op_prof(shm_buf->sess->name, ops[i], pd[i]);
2771                }
2772            }
2773
2774            if (opt_profile) {
2775                ggml_hexagon_dump_trace_events(shm_buf->sess->name, rsp, trace_events, n_traces);
2776            }
2777        }
2778    }
2779};
2780
2781void ggml_hexagon_session::flush_peers() {
2782    auto vpeers = std::move(virt_peers);
2783    virt_peers.clear();
2784    for (auto * peer : vpeers) {
2785        peer->flush_sync();
2786    }
2787
2788    auto ppeers = std::move(phys_peers);
2789    phys_peers.clear();
2790    for (auto * peer : ppeers) {
2791        peer->flush_async();
2792    }
2793
2794    for (auto & sub : this->mdev.sessions) {
2795        sub->flush_peers();
2796    }
2797}
2798
2799void ggml_hexagon_session::flush_async() {
2800    flush_peers();
2801    flush_batch();
2802}
2803
2804void ggml_hexagon_session::flush_pending(bool all) {
2805    for (auto & sub : this->mdev.sessions) {
2806        sub->flush_pending(all);
2807        if (sub->last_error > HTP_STATUS_OK) {
2808            this->last_error = sub->last_error.load();
2809        }
2810    }
2811
2812    while (this->batch_rsp_seq < this->batch_req_seq) {
2813        struct htp_opbatch_rsp rsp;
2814        uint32_t               rsp_size;
2815        uint32_t               flags;
2816
2817        struct dspqueue_buffer dbuf;
2818        uint32_t               n_dbufs;
2819
2820        // Read response packet from queue
2821        const uint32_t timeo = opt_oppoll ? 0 : DSPQUEUE_TIMEOUT;
2822
2823        int err = dspqueue_read(this->queue, &flags, 1, &n_dbufs, &dbuf, sizeof(rsp), &rsp_size, (uint8_t *) &rsp, timeo);
2824        if (err == AEE_EEXPIRED || err == AEE_EWOULDBLOCK) {
2825            continue;
2826        }
2827
2828        if (err != 0) {
2829            GGML_ABORT("ggml-hex: dspqueue_read failed: 0x%08x\n", (unsigned) err);
2830        }
2831
2832        // Basic sanity checks
2833        if (rsp_size != sizeof(rsp) || n_dbufs != 1) {
2834            GGML_ABORT("ggml-hex: %s dspcall : bad response : size %u dspbufs %u\n", this->c_name(), rsp_size, n_dbufs);
2835        }
2836
2837        if (rsp.status > HTP_STATUS_OK) {
2838            GGML_LOG_ERROR("ggml-hex: %s dspcall : dsp-rsp %s\n", this->c_name(), status_to_str(rsp.status));
2839            this->last_error = rsp.status;
2840            for (auto & sub : this->mdev.sessions) {
2841                sub->last_error = rsp.status;
2842            }
2843        }
2844
2845        op_queue->pop(rsp, dbuf);
2846
2847        GGML_ASSERT(rsp.seq == this->batch_rsp_seq + 1);
2848        this->batch_rsp_seq = rsp.seq;
2849
2850        if (!all) break;
2851    }
2852}
2853
2854void ggml_hexagon_session::flush_sync(bool all) {
2855    flush_async();
2856    flush_pending(all);
2857}
2858
2859void ggml_hexagon_session::flush_batch(size_t min_ops) {
2860    if (op_batch->n_ops < min_ops) { return; }
2861
2862    op_batch->sort_buffers();
2863
2864    htp_opbatch_req req {};
2865    dspqueue_buffer dbuf{};
2866
2867    const uint64_t seq = ++this->batch_req_seq;
2868
2869    op_batch->update_mdev_group(this->mdev.idx);
2870
2871    if (!op_queue->push(req, dbuf, op_batch, seq)) {
2872        flush_pending(false);
2873        op_queue->push(req, dbuf, op_batch, seq);
2874    }
2875
2876    for (auto & sub : this->mdev.sessions) {
2877        htp_opbatch_req sub_req {};
2878        dspqueue_buffer sub_dbuf{};
2879
2880        sub->batch_req_seq = seq;
2881        op_batch->update_mdev_group(sub->mdev.idx);
2882
2883        if (!sub->op_queue->push(sub_req, sub_dbuf, op_batch, seq)) {
2884            sub->flush_pending(false);
2885            sub->op_queue->push(sub_req, sub_dbuf, op_batch, seq);
2886        }
2887
2888        HEX_VERBOSE("ggml-hex: %s queue-opbatch: %p size %u\n", sub->c_name(), sub_dbuf.ptr, sub_dbuf.size);
2889
2890        int err = dspqueue_write(sub->queue, 0, 1, &sub_dbuf, sizeof(sub_req), (const uint8_t*) &sub_req, DSPQUEUE_TIMEOUT);
2891        if (err != 0) {
2892            GGML_ABORT("ggml-hex: %s dspqueue_write failed: 0x%08x\n", sub->c_name(), (unsigned) err);
2893        }
2894    }
2895
2896    HEX_VERBOSE("ggml-hex: %s queue-opbatch: %p size %u\n", this->c_name(), dbuf.ptr, dbuf.size);
2897
2898    int err = dspqueue_write(this->queue, 0, 1, &dbuf, sizeof(req), (const uint8_t*) &req, DSPQUEUE_TIMEOUT);
2899    if (err != 0) {
2900        GGML_ABORT("ggml-hex: %s dspqueue_write failed: 0x%08x\n", this->c_name(), (unsigned) err);
2901    }
2902
2903    op_batch->reset();
2904}
2905
2906void ggml_hexagon_session::enqueue_op(const htp_opnode & node) {
2907    auto clone_tensor_buffer = [this](const ggml_tensor * t) {
2908        if (t && t->buffer && ggml_backend_buffer_is_hexagon(t->buffer)) {
2909            auto sbuf = static_cast<const ggml_hexagon_shared_buffer *>(t->buffer->context);
2910            if (ggml_backend_hexagon_buffer_get_sess(t->buffer) != this) {
2911                this->clone_buffer(sbuf);
2912            }
2913            for (auto & sub : this->mdev.sessions) {
2914                sub->clone_buffer(sbuf);
2915            }
2916        }
2917    };
2918
2919    for (auto t : node.get_inputs()) {
2920        clone_tensor_buffer(t);
2921    }
2922    for (auto t : node.get_outputs()) {
2923        clone_tensor_buffer(t);
2924    }
2925
2926    if (opt_opfusion && op_batch->try_fuse(node)) {
2927        return;
2928    }
2929
2930    if (!op_batch->fit_op(node)) {
2931        flush_async();
2932    }
2933
2934    if (this->mdev.count > 1 && op_batch->n_ops == 0) {
2935        enqueue_mdev_group();
2936    }
2937
2938    op_batch->add_op(node);
2939}
2940
2941void ggml_hexagon_session::enqueue_mdev_group() {
2942    htp_opnode group_node(HTP_OP_MDEV_GROUP);
2943
2944    uint8_t * fence_slot = this->mdev_fence_slot;
2945
2946    static ggml_hexagon_tensor_extra fence_extra { {}, 0, GGML_HEXAGON_TENSOR_FENCE };
2947    ggml_tensor dummy_t {};
2948    dummy_t.buffer = &this->fence_buf->backend_buffer;
2949    dummy_t.extra  = &fence_extra;
2950    dummy_t.data   = (void *) fence_slot;
2951    dummy_t.type   = GGML_TYPE_I8;
2952    dummy_t.ne[0]  = HTP_FENCE_SLOT_SIZE;
2953    dummy_t.ne[1]  = (int64_t) this->mdev.count;
2954    dummy_t.ne[2]  = 1;
2955    dummy_t.ne[3]  = 1;
2956    dummy_t.nb[0]  = 1;
2957    dummy_t.nb[1]  = HTP_FENCE_SLOT_SIZE;
2958    dummy_t.nb[2]  = dummy_t.nb[1] * dummy_t.ne[1];
2959    dummy_t.nb[3]  = dummy_t.nb[2];
2960    dummy_t.op     = GGML_OP_NONE;
2961    dummy_t.op_params[0] = (int32_t) this->mdev.idx;
2962
2963    ggml_tensor * node = group_node.add_dummy(dummy_t);
2964    node->src[0] = node;
2965    group_node.init(node);
2966    group_node.outputs.clear();
2967    group_node.name = "MDEV_GROUP";
2968
2969    if (this->fence_buf->sess != this) {
2970        this->clone_buffer(this->fence_buf);
2971    }
2972    for (auto & sub : this->mdev.sessions) {
2973        sub->clone_buffer(this->fence_buf);
2974    }
2975
2976    op_batch->add_op(group_node);
2977}
2978
2979void ggml_hexagon_session::enqueue_cpy(const ggml_tensor * src, ggml_tensor * dst, const ggml_tensor * sync_tensor, uint32_t fence_seq) {
2980    const bool with_fence = sync_tensor != nullptr;
2981    htp_opnode cpy_node(with_fence ? HTP_OP_CPY_FENCE : HTP_OP_CPY);
2982
2983    ggml_tensor* node = cpy_node.add_dummy(*dst);
2984    node->op     = GGML_OP_CPY;
2985    node->src[0] = const_cast<ggml_tensor *>(src);
2986    node->src[1] = with_fence ? cpy_node.add_dummy(*sync_tensor) : nullptr;
2987    if (with_fence) {
2988        node->op_params[0] = (int32_t) fence_seq;
2989    }
2990
2991    cpy_node.init(node);
2992    if (with_fence) {
2993        cpy_node.name = "CPY+FENCE";
2994    }
2995    this->enqueue_op(cpy_node);
2996}
2997
2998void ggml_hexagon_session::enqueue_fence(const ggml_tensor * sync_tensor, uint32_t fence_seq, bool wait) {
2999    htp_opnode sync_node(HTP_OP_FENCE);
3000
3001    ggml_tensor* node = sync_node.add_dummy(*sync_tensor);
3002    node->op           = GGML_OP_NONE;
3003    node->src[0]       = node;
3004    node->op_params[0] = (int32_t) fence_seq;
3005    node->op_params[1] = wait ? 0 : 1;
3006
3007    sync_node.init(node);
3008    sync_node.name = wait ? "FENCE_WAIT" : "FENCE_SIGNAL";
3009    this->enqueue_op(sync_node);
3010}
3011
3012static bool ggml_hexagon_precompute_allreduce_params(
3013    const struct ggml_hexagon_session * sess,
3014    const struct ggml_tensor * dst,
3015    uint32_t rank,
3016    uint32_t n_ranks,
3017    bool has_add,
3018    bool is_row_bcast,
3019    struct htp_allreduce_kernel_params * kparams
3020) {
3021    memset(kparams, 0, sizeof(*kparams));
3022    kparams->rank         = (int32_t) rank;
3023    kparams->n_ranks      = (int32_t) n_ranks;
3024    kparams->is_row_bcast = (has_add && is_row_bcast) ? 1 : 0;
3025
3026    const uint32_t nelem     = (uint32_t) ggml_nelements(dst);
3027    const uint32_t elem_size = (dst->type == GGML_TYPE_F16) ? sizeof(ggml_fp16_t) : sizeof(float);
3028    const bool is_contiguous = ggml_is_contiguous(dst);
3029
3030    const uint32_t ne0 = (uint32_t) dst->ne[0];
3031    const uint32_t ne1 = (uint32_t) (dst->ne[1] * dst->ne[2] * dst->ne[3]);
3032    kparams->ne0 = (int32_t) ne0;
3033    kparams->ne1 = (int32_t) ne1;
3034
3035    const bool use_1d = is_contiguous && !(has_add && is_row_bcast && ne1 > 1);
3036
3037    if (has_add) {
3038        kparams->n_dsts = 1;
3039        if (use_1d) {
3040            kparams->rank_elem_start = 0;
3041            kparams->rank_nelem      = (int32_t) nelem;
3042        } else {
3043            kparams->rank_elem_start = 0;
3044            kparams->rank_nelem      = (int32_t) ne1;
3045        }
3046    } else {
3047        kparams->n_dsts = (int32_t) n_ranks;
3048        if (use_1d) {
3049            const uint32_t rank_chunk_elems = hex_round_up((nelem + n_ranks - 1) / n_ranks, 128);
3050            const uint32_t rank_elem_start  = (std::min)(rank * rank_chunk_elems, nelem);
3051            const uint32_t rank_elem_end    = (std::min)(rank_elem_start + rank_chunk_elems, nelem);
3052            const uint32_t rank_nelem       = rank_elem_end - rank_elem_start;
3053            kparams->rank_elem_start        = (int32_t) rank_elem_start;
3054            kparams->rank_nelem             = (int32_t) rank_nelem;
3055        } else {
3056            const uint32_t rank_chunk_rows = (ne1 + n_ranks - 1) / n_ranks;
3057            const uint32_t rank_r0         = (std::min)(rank * rank_chunk_rows, ne1);
3058            const uint32_t rank_r1         = (std::min)(rank_r0 + rank_chunk_rows, ne1);
3059            const uint32_t rank_nrows      = rank_r1 - rank_r0;
3060            kparams->rank_elem_start       = (int32_t) rank_r0;
3061            kparams->rank_nelem            = (int32_t) rank_nrows;
3062        }
3063    }
3064
3065    if (use_1d) {
3066        const uint32_t rank_nelem = (uint32_t) kparams->rank_nelem;
3067        const uint32_t n_threads  = (std::min)((uint32_t) sess->n_threads, (std::max)(1u, rank_nelem / 128));
3068        kparams->n_threads = n_threads;
3069        const size_t n_vtcm_buffers = htp_allreduce_vtcm_buffer_count(n_ranks, n_threads, has_add, is_row_bcast);
3070
3071        uint32_t block_elems = 65536;
3072        if (block_elems > rank_nelem / n_threads && rank_nelem / n_threads > 128) {
3073            block_elems = hex_round_up(rank_nelem / (n_threads * 2), 128);
3074        }
3075        block_elems = (std::max)(128u, block_elems);
3076
3077        kparams->block_elems          = block_elems;
3078        kparams->vtcm_size_per_thread = 2 * block_elems * elem_size;
3079        kparams->vtcm_size            = n_vtcm_buffers * kparams->vtcm_size_per_thread;
3080
3081        while ((size_t) kparams->vtcm_size > sess->vtcm_size && block_elems > 128) {
3082            const size_t max_bytes_per_buf = sess->vtcm_size / (n_vtcm_buffers * 2);
3083            block_elems = (uint32_t) hex_align_down((size_t) (max_bytes_per_buf / elem_size), 128);
3084            if (block_elems < 128) break;
3085            kparams->block_elems          = block_elems;
3086            kparams->vtcm_size_per_thread = 2 * block_elems * elem_size;
3087            kparams->vtcm_size            = n_vtcm_buffers * kparams->vtcm_size_per_thread;
3088        }
3089
3090        if (sess->vtcm_size < (size_t) kparams->vtcm_size || block_elems < 128) {
3091            HEX_VERBOSE("ggml-hex: %s allreduce 1D solver failed to fit VTCM (%d > %zu)\n",
3092                        sess->c_name(), kparams->vtcm_size, sess->vtcm_size);
3093            return false;
3094        }
3095
3096        kparams->elems_per_thread = hex_round_up((rank_nelem + n_threads - 1) / n_threads, block_elems);
3097        kparams->kernel_type      = HTP_ALLREDUCE_KERNEL_DMA_1D;
3098        return true;
3099    } else {
3100        const uint32_t rank_nrows = (uint32_t) kparams->rank_nelem;
3101        const uint32_t n_threads  = (std::min)((uint32_t) sess->n_threads, (std::max)(1u, rank_nrows));
3102        kparams->n_threads = n_threads;
3103        const size_t n_vtcm_buffers = htp_allreduce_vtcm_buffer_count(n_ranks, n_threads, has_add, is_row_bcast);
3104
3105        const uint32_t row_bytes = ne0 * elem_size;
3106        const uint32_t row_size_aligned = (uint32_t) hex_align_up(row_bytes, 128);
3107        kparams->row_size_aligned = row_size_aligned;
3108
3109        const uint32_t nrows_per_thread = (rank_nrows + n_threads - 1) / n_threads;
3110        uint32_t block_rows = (std::min)(128u, nrows_per_thread);
3111        block_rows = (std::max)(1u, block_rows);
3112        kparams->block_elems = block_rows;
3113
3114        kparams->vtcm_size_per_thread = 2 * (block_rows * row_size_aligned);
3115        kparams->vtcm_size            = n_vtcm_buffers * kparams->vtcm_size_per_thread;
3116
3117        while ((size_t) kparams->vtcm_size > sess->vtcm_size && block_rows > 1) {
3118            const size_t max_rows_per_buf = sess->vtcm_size / (n_vtcm_buffers * 2 * row_size_aligned);
3119            block_rows = (std::max)(1u, (uint32_t) max_rows_per_buf);
3120            kparams->block_elems          = block_rows;
3121            kparams->vtcm_size_per_thread = 2 * (block_rows * row_size_aligned);
3122            kparams->vtcm_size            = n_vtcm_buffers * kparams->vtcm_size_per_thread;
3123            if (max_rows_per_buf == 0) break;
3124        }
3125
3126        if (sess->vtcm_size < (size_t) kparams->vtcm_size || block_rows < 1) {
3127            HEX_VERBOSE("ggml-hex: %s allreduce 2D solver failed to fit VTCM (%d > %zu)\n",
3128                        sess->c_name(), kparams->vtcm_size, sess->vtcm_size);
3129            return false;
3130        }
3131
3132        kparams->elems_per_thread = nrows_per_thread;
3133        kparams->kernel_type      = HTP_ALLREDUCE_KERNEL_DMA_2D;
3134        return true;
3135    }
3136}
3137
3138void ggml_hexagon_session::enqueue_allreduce(
3139    const ggml_tensor * dst,
3140    const std::vector<const ggml_tensor *> & src_tensors,
3141    const std::vector<const ggml_tensor *> & sync_tensors,
3142    uint32_t rank,
3143    uint32_t n_ranks,
3144    uint32_t fence_seq_entry,
3145    uint32_t fence_seq_exit
3146) {
3147    htp_opnode ar_node(HTP_OP_ALLREDUCE);
3148
3149    ggml_tensor* node = ar_node.add_dummy(*dst);
3150    node->op           = GGML_OP_NONE;
3151    node->op_params[0] = (int32_t) fence_seq_entry;
3152    node->op_params[1] = (int32_t) fence_seq_exit;
3153
3154    ar_node.init(node);
3155
3156    ar_node.inputs.clear();
3157    for (size_t i = 0; i < src_tensors.size(); i++) {
3158        ar_node.inputs.push_back(src_tensors[i]);
3159    }
3160    for (size_t i = 0; i < sync_tensors.size(); i++) {
3161        ar_node.inputs.push_back(ar_node.add_dummy(*sync_tensors[i]));
3162    }
3163
3164    ar_node.outputs.clear();
3165    for (size_t i = 0; i < src_tensors.size(); i++) {
3166        ar_node.outputs.push_back(src_tensors[i]);
3167    }
3168
3169    ggml_hexagon_precompute_allreduce_params(
3170        this, dst, rank, n_ranks, false, false,
3171        (struct htp_allreduce_kernel_params *) ar_node.kernel_params
3172    );
3173
3174    ar_node.name = "ALLREDUCE";
3175    this->enqueue_op(ar_node);
3176}
3177
3178bool ggml_hexagon_session::clone_buffer(const ggml_hexagon_shared_buffer *sbuf)
3179{
3180    GGML_ASSERT(sbuf && sbuf->mem);
3181    if (sbuf->sess == this) return true;
3182
3183    auto mem = sbuf->mem;
3184    int   fd = mem->fd;
3185
3186    GGML_ASSERT(fd >= 0);
3187
3188    if (this->cloned_buffers.find(fd) != this->cloned_buffers.end()) return true;
3189
3190    HEX_VERBOSE("ggml-hex: %s clone-buffer: %s base %p size %zu fd %d\n", this->name.c_str(),
3191                sbuf->c_name(), sbuf->base(), sbuf->size(), fd);
3192
3193    auto clone = std::make_unique<ggml_hexagon_shared_buffer>(this, *sbuf);
3194    try {
3195        clone->mmap();
3196    } catch (const std::exception & exc) {
3197        GGML_LOG_ERROR("ggml-hex: %s lazy mapping of buffer context failed: %s\n", this->c_name(), exc.what());
3198        return false;
3199    }
3200
3201    this->cloned_buffers[fd] = std::move(clone);
3202    mem->mapped_clones.insert(this);
3203    return true;
3204}
3205
3206void ggml_hexagon_session::release_buffer(const ggml_hexagon_shared_buffer * sbuf) {
3207    GGML_ASSERT(sbuf && sbuf->mem);
3208
3209    auto mem = sbuf->mem;
3210    int   fd = mem->fd;
3211
3212    GGML_ASSERT(fd >= 0);
3213
3214    auto it = this->cloned_buffers.find(fd);
3215    if (it != this->cloned_buffers.end()) {
3216        auto clone = std::move(it->second);
3217        this->cloned_buffers.erase(it);
3218    }
3219    mem->mapped_clones.erase(this);
3220}
3221
3222void ggml_hexagon_session::unclone_buffer(const ggml_hexagon_shared_buffer * sbuf) {
3223    GGML_ASSERT(sbuf && sbuf->mem);
3224
3225    auto mem = sbuf->mem;
3226    std::vector<ggml_hexagon_session *> sessions(mem->mapped_clones.begin(), mem->mapped_clones.end());
3227
3228    for (auto * sess : sessions) {
3229        sess->release_buffer(sbuf);
3230    }
3231}
3232
3233static size_t ggml_hexagon_measure_max_vmem(ggml_hexagon_session *sess) {
3234    // Allocate a bunch pinned buffers till failure.
3235    // This is kind of expensive but handy for figuring out exactly how much we can mmap on a specific device.
3236    // Typically we're going to allocate all/most of these buffers anyway for the model weights.
3237
3238    std::vector<ggml_hexagon_shared_buffer *> sbufs;
3239
3240    const size_t MiB = 1024 * 1024;
3241    const size_t GiB = MiB  * 1024;
3242
3243    size_t vmem = 0;
3244    size_t step = 256u * MiB;
3245
3246    try {
3247        sbufs.push_back(new ggml_hexagon_shared_buffer(sess, GiB, true)); vmem += GiB;
3248        sbufs.push_back(new ggml_hexagon_shared_buffer(sess, GiB, true)); vmem += GiB;
3249        sbufs.push_back(new ggml_hexagon_shared_buffer(sess, GiB, true)); vmem += GiB;
3250
3251        while (1) {
3252            sbufs.push_back(new ggml_hexagon_shared_buffer(sess, step, true));
3253            vmem += step;
3254        }
3255    } catch (...) { }
3256
3257    for (auto b : sbufs) { delete b; }
3258
3259    return vmem - step; // backoff to account for overhead from internal mappings
3260}
3261
3262void ggml_hexagon_session::allocate(const ggml_hexagon_device_config & config) noexcept(false) {
3263    int phys_idx = config.physical_idx;
3264    int virt_idx = config.virtual_idx;
3265
3266    this->valid_session = false;
3267    this->valid_handle  = false;
3268    this->valid_queue   = false;
3269    this->valid_iface   = false;
3270
3271    this->name          = config.name;
3272    this->phys_idx      = phys_idx;
3273    this->virt_idx      = virt_idx;
3274    this->domain_id     = config.domain_id;
3275    this->session_id    = 0;
3276    this->batch_req_seq = 0;
3277    this->batch_rsp_seq = 0;
3278    this->last_error    = HTP_STATUS_OK;
3279
3280    GGML_LOG_DEBUG("ggml-hex: %s allocating new session : domain %u phys-idx %u virt-idx %u\n", this->name.c_str(), this->domain_id, phys_idx, virt_idx);
3281
3282    if (config.domain_id < 0 || config.domain_name.empty()) {
3283        GGML_LOG_ERROR("ggml-hex: %s: invalid physical CDSP core %d\n", config.name.c_str(), config.physical_idx);
3284        throw std::runtime_error("ggml-hex: invalid physical CDSP core");
3285    }
3286
3287    const std::string & dom_name = config.domain_name;
3288
3289    // Create new session if virtual_idx > 0
3290    if (virt_idx > 0) {
3291        struct remote_rpc_reserve_new_session n {};
3292        n.domain_name_len  = dom_name.size();
3293        n.domain_name      = const_cast<char *>(dom_name.c_str());
3294        n.session_name     = const_cast<char *>(this->name.c_str());
3295        n.session_name_len = this->name.size();
3296        n.session_id       = virt_idx;
3297
3298        int err = remote_session_control(FASTRPC_RESERVE_NEW_SESSION, (void *) &n, sizeof(n));
3299        if (err != AEE_SUCCESS) {
3300            GGML_LOG_ERROR("ggml-hex: %s failed to reserve new session (physical %d, virtual %d) : error 0x%x\n",
3301                           this->c_name(), phys_idx, virt_idx, err);
3302            throw std::runtime_error("ggml-hex: remote_session_control(new-sess) failed (see log for details)");
3303        }
3304
3305        // Save the IDs
3306        this->session_id    = n.session_id;
3307        this->domain_id     = n.effective_domain_id;
3308        this->valid_session = true;
3309    } else {
3310        struct remote_rpc_effective_domain_id eff {};
3311        eff.domain_name     = const_cast<char *>(dom_name.c_str());
3312        eff.domain_name_len = dom_name.size();
3313        eff.session_id      = 0;
3314
3315        int err = remote_session_control(FASTRPC_GET_EFFECTIVE_DOMAIN_ID, (void *) &eff, sizeof(eff));
3316        if (err == AEE_SUCCESS) {
3317            this->domain_id = eff.effective_domain_id;
3318        } else {
3319            GGML_LOG_DEBUG("ggml-hex: %s FASTRPC_GET_EFFECTIVE_DOMAIN_ID returned 0x%x, using domain_id %d\n",
3320                           this->name.c_str(), err, this->domain_id);
3321        }
3322    }
3323
3324    // Enable unsigned modules
3325    {
3326        struct remote_rpc_control_unsigned_module u;
3327        u.domain = this->domain_id;
3328        u.enable = 1;
3329        int err  = remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, (void *) &u, sizeof(u));
3330        if (err != AEE_SUCCESS) {
3331            GGML_LOG_ERROR("ggml-hex: %s failed to enable unsigned PD : error 0x%x\n", this->c_name(), err);
3332            throw std::runtime_error("ggml-hex: remote_session_control(unsign) failed (see log for details)");
3333        }
3334    }
3335
3336    char session_uri[256];
3337    {
3338        char htp_uri[256];
3339        snprintf(htp_uri, sizeof(htp_uri), "file:///libggml-htp-v%u.so?htp_iface_skel_handle_invoke&_modver=1.0", opt_arch);
3340
3341        struct remote_rpc_get_uri u = {};
3342        u.session_id      = this->session_id;
3343        u.domain_name     = const_cast<char *>(dom_name.c_str());
3344        u.domain_name_len = dom_name.size();
3345        u.module_uri      = const_cast<char *>(htp_uri);
3346        u.module_uri_len  = strlen(htp_uri);
3347        u.uri             = session_uri;
3348        u.uri_len         = sizeof(session_uri);
3349
3350        int err = remote_session_control(FASTRPC_GET_URI, (void *) &u, sizeof(u));
3351        if (err != AEE_SUCCESS) {
3352            snprintf(session_uri, sizeof(session_uri), "%s&_dom=%s&_session=%u",
3353                     htp_uri, dom_name.c_str(), this->session_id);
3354
3355            GGML_LOG_WARN("ggml-hex: %s failed to get URI (physical %d, virtual %d) : error 0x%x. Falling back to single session URI: %s\n",
3356                          this->c_name(), phys_idx, virt_idx, err, session_uri);
3357        }
3358    }
3359
3360    // Open session
3361    int err = htp_iface_open(session_uri, &this->handle);
3362    if (err != AEE_SUCCESS) {
3363        GGML_LOG_ERROR("ggml-hex: %s failed to open session : uri %s error 0x%x\n", this->c_name(), session_uri, err);
3364        throw std::runtime_error("ggml-hex: failed to open session (see log for details)");
3365    }
3366
3367    this->valid_handle = true;
3368
3369    // Query HW info and resolve session options
3370    this->max_bufsize = opt_mbuf;
3371    {
3372        unsigned int hw_n_threads = 0;
3373        unsigned int hw_n_hvx     = 0;
3374        unsigned int hw_n_hmx     = 0;
3375        unsigned long long hw_vtcm_size = 0;
3376        int hw_err = htp_iface_hwinfo(this->handle, &hw_n_threads, &hw_n_hvx, &hw_n_hmx, &hw_vtcm_size);
3377        if (hw_err == 0) {
3378            const uint32_t max_n_threads = (std::min)((uint32_t) HTP_MAX_NTHREADS, (uint32_t) hw_n_threads);
3379            this->n_threads = opt_nhvx > 0 ? (uint32_t) (std::min)(opt_nhvx, (size_t) max_n_threads) : max_n_threads;
3380            this->n_hvx     = this->n_threads;
3381            this->n_hmx     = (opt_nhmx != 0) ? (uint32_t)hw_n_hmx : 0;
3382            this->vtcm_size = (uint64_t)hw_vtcm_size;
3383            GGML_LOG_INFO("ggml-hex: %s hwinfo: threads %u, hvx %u, hmx %u, vtcm %llu MB\n",
3384                          this->c_name(), this->n_threads, this->n_hvx, this->n_hmx,
3385                          (unsigned long long)(this->vtcm_size / (1024 * 1024)));
3386        } else {
3387            GGML_LOG_WARN("ggml-hex: %s failed to query hwinfo (0x%x), using defaults\n", this->c_name(), hw_err);
3388            const uint32_t default_n_threads = (std::min)(8u, (uint32_t) HTP_MAX_NTHREADS);
3389            this->n_threads = opt_nhvx > 0 ? (uint32_t) (std::min)(opt_nhvx, (size_t) HTP_MAX_NTHREADS) : default_n_threads;
3390            this->n_hvx     = this->n_threads;
3391            this->n_hmx     = (opt_nhmx != 0) ? 1 : 0;
3392            this->vtcm_size = 8 * 1024 * 1024;
3393        }
3394    }
3395
3396    // Enable FastRPC QoS mode
3397    {
3398        struct remote_rpc_control_latency l;
3399        l.enable = 1;
3400
3401        int err = remote_handle64_control(this->handle, DSPRPC_CONTROL_LATENCY, (void *) &l, sizeof(l));
3402        if (err != 0) {
3403            GGML_LOG_WARN("ggml-hex: failed to enable fastrpc QOS mode: 0x%08x\n", (unsigned) err);
3404        }
3405    }
3406
3407    GGML_LOG_INFO("ggml-hex: %s new session : session-id %d domain-id %d uri %s handle 0x%lx\n", this->c_name(),
3408                  this->session_id, this->domain_id, session_uri, (unsigned long) this->handle);
3409
3410    const size_t req_q_size = (sizeof(htp_opbatch_req) * opt_opqueue * 2) + 1024;
3411    const size_t rsp_q_size = (sizeof(htp_opbatch_rsp) * opt_opqueue * 2) + 1024;
3412
3413    // Now let's setup the DSP queue
3414    err = dspqueue_create(this->domain_id,
3415                          0,              // Flags
3416                          req_q_size,     // Request  queue size (in bytes)
3417                          rsp_q_size,     // Response queue size (in bytes)
3418                          nullptr,        // Read packet callback (we handle reads explicitly)
3419                          nullptr,        // Error callback (we handle errors during reads)
3420                          (void *) this,  // Callback context
3421                          &queue);
3422    if (err != 0) {
3423        GGML_LOG_ERROR("ggml-hex: %s dspqueue_create failed: 0x%08x\n", this->name.c_str(), (unsigned) err);
3424        throw std::runtime_error("ggml-hex: failed to create dspqueue (see log for details)");
3425    }
3426
3427    this->valid_queue = true;
3428
3429    // Export queue for use on the DSP
3430    err = dspqueue_export(queue, &this->queue_id);
3431    if (err != 0) {
3432        GGML_LOG_ERROR("ggml-hex: dspqueue_export failed: 0x%08x\n", (unsigned) err);
3433        throw std::runtime_error("ggml-hex: dspqueue export failed (see log for details)");
3434    }
3435
3436    if (opt_etm) {
3437        err = htp_iface_etm(this->handle, 1);
3438        if (err != 0) {
3439            GGML_LOG_ERROR("ggml-hex: failed to enable ETM tracing: 0x%08x\n", (unsigned) err);
3440        }
3441    }
3442
3443    // Allocate buffers and state for op batching
3444    this->op_queue = new ggml_hexagon_opqueue(this, opt_opbatch, opt_opqueue);
3445
3446    this->fence_buf = new ggml_hexagon_fence_buffer(this, &dev_ctx->fence_buffer_type, 64 * 1024);
3447    if (this->mdev.count > 1) {
3448        this->mdev_fence_slot = this->alloc_fence(this->mdev.count);
3449    }
3450
3451    if (!opt_vmem) {
3452        opt_vmem = ggml_hexagon_measure_max_vmem(this);
3453        GGML_LOG_INFO("ggml-hex: %s measured max vmem %zu\n", this->c_name(), opt_vmem);
3454    }
3455    const size_t shm_size = this->op_queue->shm_size();
3456    this->max_vmem = (opt_vmem > shm_size) ? (opt_vmem - shm_size) : opt_vmem;
3457
3458    this->op_batch = new ggml_hexagon_opbatch(this, opt_opbatch, this->max_vmem);
3459
3460    // Start dspqueue/opbatch processing
3461    err = htp_iface_start(this->handle, this->session_id, this->queue_id, this->n_threads, opt_nhmx, this->max_vmem);
3462    if (err != 0) {
3463        GGML_LOG_ERROR("ggml-hex: %s failed to start session: 0x%08x\n", this->c_name(), (unsigned) err);
3464        throw std::runtime_error("ggml-hex: iface start failed (see log for details)");
3465    }
3466    this->valid_iface = true;
3467
3468    if (opt_profile) {
3469        htp_iface_pmu_conf pmu_conf{};
3470        std::copy(opt_pmu_evt.begin(), opt_pmu_evt.end(), pmu_conf.events);
3471
3472        err = htp_iface_profiler(this->handle, opt_profile, &pmu_conf);
3473        if (err != 0) {
3474            GGML_LOG_ERROR("ggml-hex: failed to enable profiling: 0x%08x\n", (unsigned) err);
3475        }
3476    }
3477}
3478
3479void ggml_hexagon_session::release() noexcept(true) {
3480    GGML_LOG_INFO("ggml-hex: releasing session: %s\n", this->name.c_str());
3481
3482    this->mdev.sessions.clear();
3483
3484    int err;
3485
3486    if (this->valid_iface) {
3487        // Stop dspqueue/opbatch processing
3488        err = htp_iface_stop(this->handle);
3489        if (err != 0) {
3490            GGML_ABORT("ggml-hex: htp_iface_stop failed: 0x%08x\n", (unsigned) err);
3491        }
3492    }
3493
3494    delete this->op_batch;
3495    delete this->op_queue;
3496    for (auto & it : this->cpy_fence_slots) {
3497        free_fence((void *) it.second, 1);
3498    }
3499    this->cpy_fence_slots.clear();
3500
3501    if (this->fence_buf) {
3502        unclone_buffer(this->fence_buf);
3503        delete this->fence_buf;
3504        this->fence_buf = nullptr;
3505    }
3506    while (!this->cloned_buffers.empty()) {
3507        release_buffer(this->cloned_buffers.begin()->second.get());
3508    }
3509
3510    if (opt_etm) {
3511        err = htp_iface_etm(this->handle, 0);
3512        if (err != 0) {
3513            GGML_LOG_ERROR("ggml-hex: warn : failed to disable ETM tracing: 0x%08x\n", (unsigned) err);
3514        }
3515    }
3516
3517    if (opt_profile) {
3518        htp_iface_pmu_conf pmu_conf{};
3519        err = htp_iface_profiler(this->handle, 0, &pmu_conf);
3520        if (err != 0) {
3521            GGML_LOG_ERROR("ggml-hex: warn : failed to disable profiling: 0x%08x\n", (unsigned) err);
3522        }
3523    }
3524
3525    if (this->valid_queue) {
3526        err = dspqueue_close(queue);
3527        if (err != 0) {
3528            GGML_ABORT("ggml-hex: dspqueue_close failed: 0x%08x\n", (unsigned) err);
3529        }
3530    }
3531
3532    if (this->valid_handle) {
3533        htp_iface_close(this->handle);
3534    }
3535}
3536
3537ggml_hexagon_session::ggml_hexagon_session(const ggml_hexagon_device_config & config, ggml_backend_dev_t dev, uint32_t mdev_idx, uint32_t mdev_count) noexcept(false) {
3538    this->dev        = dev;
3539    this->dev_ctx    = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
3540    this->mdev.idx   = mdev_idx;
3541    this->mdev.count = mdev_count > 0 ? mdev_count : (uint32_t) (1 + config.mdev_group.size());
3542    op_batch         = nullptr;
3543    op_queue         = nullptr;
3544    fence_buf        = nullptr;
3545    fence_seq        = ((uintptr_t)this) & 0xFFFF;
3546
3547    try {
3548        allocate(config);
3549        if (this->mdev.idx == 0 && !config.mdev_group.empty()) {
3550            for (size_t i = 0; i < config.mdev_group.size(); i++) {
3551                this->mdev.sessions.push_back(std::make_unique<ggml_hexagon_session>(
3552                    config.mdev_group[i], this->dev, (uint32_t) (i + 1), this->mdev.count));
3553            }
3554        }
3555    } catch (const std::exception & exc) {
3556        release();
3557        throw;
3558    }
3559}
3560
3561ggml_hexagon_session::~ggml_hexagon_session() noexcept(true) {
3562    release();
3563}
3564
3565// ** backend interface
3566
3567static bool ggml_hexagon_flash_attn_is_hmx_eligible(
3568    const struct ggml_hexagon_session * sess,
3569    const struct ggml_tensor * q,
3570    const struct ggml_tensor * k,
3571    const struct ggml_tensor * v,
3572    const struct ggml_tensor * sinks
3573) {
3574    if (sess->n_hmx == 0) {
3575        return false;
3576    }
3577
3578    if (opt_fa_select < 2) {
3579        return false;
3580    }
3581
3582    if ((k->type != GGML_TYPE_F16 && k->type != GGML_TYPE_Q8_0) ||
3583        (v->type != GGML_TYPE_F16 && v->type != GGML_TYPE_Q8_0)) {
3584        return false;
3585    }
3586
3587    const uint32_t DK = q->ne[0];
3588    const uint32_t DV = v->ne[0];
3589
3590    if (DK % 64 != 0 || DV % 64 != 0) {
3591        return false;
3592    }
3593
3594    // Fall back to HVX for small token counts if head dimension is small (DK <= 128)
3595    const uint32_t neq1 = q->ne[1];
3596    if (DK <= 128 && neq1 < 5) {
3597        return false;
3598    }
3599
3600    return true;
3601
3602    GGML_UNUSED(sinks);
3603}
3604
3605static bool ggml_hexagon_precompute_flash_attn_params(
3606    const struct ggml_hexagon_session * sess,
3607    const struct ggml_tensor * op,
3608    struct htp_fa_kernel_params * kparams
3609) {
3610    if (opt_fa_select < 1) {
3611        return false;
3612    }
3613
3614    memset(kparams, 0, sizeof(*kparams));
3615
3616    const struct ggml_tensor * q    = op->src[0];
3617    const struct ggml_tensor * k    = op->src[1];
3618    const struct ggml_tensor * v    = op->src[2];
3619    const struct ggml_tensor * mask = op->src[3];
3620    const struct ggml_tensor * dst  = op;
3621
3622    const uint32_t neq0 = q->ne[0];  // head_dim (DK)
3623    const uint32_t neq1 = q->ne[1];  // n_tokens
3624    const uint32_t neq2 = q->ne[2];  // n_heads
3625
3626    const uint32_t nek1 = k->ne[1];  // kv_len
3627
3628    const uint32_t nev0 = v->ne[0];  // head_dim (DV)
3629
3630    const uint32_t DK = neq0;
3631    const uint32_t DV = nev0;
3632
3633    const uint32_t n_kv_heads = k->ne[2];
3634    const uint32_t G          = neq2 / n_kv_heads;
3635
3636    float scale         = 1.0f;
3637    float max_bias      = 0.0f;
3638    float logit_softcap = 0.0f;
3639    memcpy(&scale,         &op->op_params[0], sizeof(float));
3640    memcpy(&max_bias,      &op->op_params[1], sizeof(float));
3641    memcpy(&logit_softcap, &op->op_params[2], sizeof(float));
3642
3643    if (logit_softcap != 0.0f) {
3644        scale /= logit_softcap;
3645    }
3646
3647    kparams->scale = scale;
3648    kparams->max_bias = max_bias;
3649    kparams->logit_softcap = logit_softcap;
3650
3651    kparams->is_q_fp32 = (q->type == GGML_TYPE_F32) ? 1 : 0;
3652    kparams->is_dst_fp32 = (dst->type == GGML_TYPE_F32) ? 1 : 0;
3653    kparams->G = G;
3654
3655    const uint32_t n_head = q->ne[2];
3656    kparams->n_head_log2 = 1u << (uint32_t) std::floor(std::log2(n_head));
3657    kparams->m0 = std::pow(2.0f, -(max_bias) / kparams->n_head_log2);
3658    kparams->m1 = std::pow(2.0f, -(max_bias / 2.0f) / kparams->n_head_log2);
3659
3660    // Check HMX eligibility
3661    const struct ggml_tensor * sinks = op->src[4];
3662    if (ggml_hexagon_flash_attn_is_hmx_eligible(sess, q, k, v, sinks)) {
3663        size_t Br = 0, Bc = 0;
3664        int ret = hmx_fa_find_chunk_size(&Br, &Bc, G, DK, DV, neq1, nek1, sess->vtcm_size, sess->n_threads, kparams->is_q_fp32 != 0);
3665        if (ret == 0) {
3666            kparams->kernel_type = HTP_FA_KERNEL_HMX;
3667            kparams->Br = Br;
3668            kparams->Bc = Bc;
3669            kparams->n_kv_blocks = (nek1 + Bc - 1) / Bc;
3670            kparams->n_threads = (kparams->n_kv_blocks >= 3 && sess->n_threads >= 2) ? sess->n_threads : 1;
3671
3672            kparams->u.hmx.g_br = hex_align_up(G * Br, 32);
3673            kparams->u.hmx.pipeline = (kparams->n_kv_blocks >= 3 && sess->n_threads >= 2) ? 1 : 0;
3674            kparams->vtcm_size = hmx_fa_compute_vtcm_usage(G, DK, DV, Br, Bc, kparams->n_threads, kparams->u.hmx.pipeline != 0, kparams->is_q_fp32 != 0);
3675
3676            const size_t row_vec_bytes = hex_align_up(Bc * sizeof(uint16_t), 256);
3677            kparams->u.hmx.row_buf_stride = row_vec_bytes / 128; // HVX vector is 128 bytes
3678
3679            const size_t m_line_bytes = hex_align_up(Bc * sizeof(uint16_t), 128);
3680            kparams->u.hmx.mask_buf_row_stride = m_line_bytes / sizeof(uint16_t);
3681            kparams->u.hmx.mask_broadcast = (mask != nullptr && mask->ne[2] == 1) ? 1 : 0;
3682            kparams->u.hmx.div_G = init_fastdiv_values(G);
3683            if (mask) {
3684                kparams->src3_div2 = init_fastdiv_values(mask->ne[2]);
3685                kparams->src3_div3 = init_fastdiv_values(mask->ne[3]);
3686            }
3687
3688            kparams->qrows = 0;
3689            kparams->qrows_per_thread = 0;
3690            return true;
3691        }
3692    }
3693
3694    // Fallback to HVX
3695    kparams->kernel_type = HTP_FA_KERNEL_HVX;
3696    kparams->Br = 1;
3697    kparams->Bc = 64; // FLASH_ATTN_BLOCK_SIZE
3698    kparams->n_kv_blocks = (k->ne[1] + 64 - 1) / 64;
3699    kparams->n_threads = sess->n_threads;
3700
3701    const size_t size_q_row_padded = hex_round_up(q->ne[0] * (kparams->is_q_fp32 ? 4 : 2), 128);
3702    const size_t size_k_row_padded = hex_round_up(k->ne[0] * 2, 128);
3703    const size_t size_v_row_padded = hex_round_up(v->ne[0] * 2, 128);
3704
3705    kparams->vtcm_size = hvx_fa_compute_vtcm_usage(DK, DV, kparams->is_q_fp32 != 0, mask != nullptr, sess->n_threads);
3706
3707    kparams->u.hvx.size_q_row_padded = size_q_row_padded;
3708    kparams->u.hvx.size_k_row_padded = size_k_row_padded;
3709    kparams->u.hvx.size_v_row_padded = size_v_row_padded;
3710    kparams->u.hvx.src0_div21 = init_fastdiv_values(q->ne[2] * q->ne[1]);
3711    kparams->u.hvx.src0_div1 = init_fastdiv_values(q->ne[1]);
3712    kparams->broadcast_rk2 = init_fastdiv_values(q->ne[2]/k->ne[2]);
3713    kparams->broadcast_rk3 = init_fastdiv_values(q->ne[3]/k->ne[3]);
3714    kparams->broadcast_rv2 = init_fastdiv_values(q->ne[2]/v->ne[2]);
3715    kparams->broadcast_rv3 = init_fastdiv_values(q->ne[3]/v->ne[3]);
3716    if (mask) {
3717        kparams->src3_div2 = init_fastdiv_values(mask->ne[2]);
3718        kparams->src3_div3 = init_fastdiv_values(mask->ne[3]);
3719    }
3720
3721    kparams->qrows = q->ne[1] * q->ne[2] * q->ne[3];
3722    kparams->qrows_per_thread = (kparams->qrows + sess->n_threads - 1) / sess->n_threads;
3723
3724    return true;
3725}
3726
3727static bool ggml_hexagon_supported_flash_attn_ext(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
3728    const struct ggml_tensor * src0 = op->src[0];
3729    const struct ggml_tensor * src1 = op->src[1];
3730    const struct ggml_tensor * src2 = op->src[2];
3731    const struct ggml_tensor * src3 = op->src[3];
3732    const struct ggml_tensor * src4 = op->src[4];
3733    const struct ggml_tensor * dst  = op;
3734
3735    // Check for F16/Q8_0 support
3736    if ((src0->type != GGML_TYPE_F16 && src0->type != GGML_TYPE_F32) ||
3737        (src1->type != GGML_TYPE_F16 && src1->type != GGML_TYPE_Q8_0) ||
3738        (src2->type != GGML_TYPE_F16 && src2->type != GGML_TYPE_Q8_0)) {
3739        return false;
3740    }
3741
3742    if (src3 && src3->type != GGML_TYPE_F16) {  // mask
3743        return false;
3744    }
3745
3746    if (src4 && src4->type != GGML_TYPE_F32) {  // sinks
3747        return false;
3748    }
3749
3750    // For now we support F32 or F16 output as htp backend often converts output on the fly if needed,
3751    // but the op implementation writes to F16 or F32.
3752    // Let's assume dst can be F32 or F16.
3753    if (dst->type != GGML_TYPE_F32 && dst->type != GGML_TYPE_F16) {
3754        return false;
3755    }
3756
3757    if (dst->ne[3] != 1) {
3758        return false;
3759    }
3760
3761    struct htp_fa_kernel_params kparams;
3762    if (!ggml_hexagon_precompute_flash_attn_params(sess, op, &kparams)) {
3763        return false;
3764    }
3765
3766    if ((size_t) kparams.vtcm_size > sess->vtcm_size) {
3767        HEX_VERBOSE("ggml-hex: skip flash_attn_ext because VTCM needed (%d) > budget (%zu)\n",
3768                    kparams.vtcm_size, sess->vtcm_size);
3769        return false;
3770    }
3771
3772    return true;
3773}
3774
3775static bool ggml_hexagon_supported_gated_delta_net(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
3776    const struct ggml_tensor * q     = op->src[0];
3777    const struct ggml_tensor * k     = op->src[1];
3778    const struct ggml_tensor * v     = op->src[2];
3779    const struct ggml_tensor * g     = op->src[3];
3780    const struct ggml_tensor * beta  = op->src[4];
3781    const struct ggml_tensor * state = op->src[5];
3782    const struct ggml_tensor * dst   = op;
3783
3784    if (q->type != GGML_TYPE_F32 || k->type != GGML_TYPE_F32 || v->type != GGML_TYPE_F32 ||
3785        g->type != GGML_TYPE_F32 || beta->type != GGML_TYPE_F32 || state->type != GGML_TYPE_F32 ||
3786        dst->type != GGML_TYPE_F32) {
3787        return false;
3788    }
3789
3790    if (!ggml_is_contiguous_rows(q) || !ggml_is_contiguous_rows(k) || !ggml_is_contiguous_rows(v) ||
3791        !ggml_is_contiguous(g) || !ggml_is_contiguous(beta) || !ggml_is_contiguous(state) ||
3792        !ggml_is_contiguous(dst)) {
3793        return false;
3794    }
3795
3796    const int64_t S_v      = v->ne[0];
3797    const int64_t H        = v->ne[1];
3798    const int64_t n_tokens = v->ne[2];
3799    const int64_t n_seqs   = v->ne[3];
3800    const int64_t K        = ggml_get_op_params_i32(op, 0);
3801
3802    if (S_v <= 0 || S_v > 128 || H <= 0 || n_tokens <= 0 || n_seqs <= 0) {
3803        return false;
3804    }
3805    if (q->ne[0] != S_v || k->ne[0] != S_v || q->ne[1] <= 0 || k->ne[1] <= 0 ||
3806        q->ne[2] != n_tokens || k->ne[2] != n_tokens || q->ne[3] <= 0 || k->ne[3] <= 0 ||
3807        (n_seqs % q->ne[3]) != 0 || (n_seqs % k->ne[3]) != 0) {
3808        return false;
3809    }
3810    if ((g->ne[0] != 1 && g->ne[0] != S_v) || beta->ne[0] != 1) {
3811        return false;
3812    }
3813    // state holds s0 only [S_v, S_v, H, n_seqs]; K is op param 0.
3814    if (ggml_nelements(state) != S_v * S_v * H * n_seqs) {
3815        return false;
3816    }
3817    if (dst->ne[0] != S_v * H || dst->ne[1] != n_tokens * n_seqs + S_v * n_seqs * K) {
3818        return false;
3819    }
3820
3821    return true;
3822
3823    GGML_UNUSED(sess);
3824}
3825
3826static bool ggml_hexagon_matmul_is_hmx_eligible(
3827    const struct ggml_tensor * src0,
3828    const struct ggml_tensor * src1,
3829    const struct ggml_tensor * dst,
3830    int ne01_padded,
3831    bool is_matmul_id,
3832    bool is_batched
3833) {
3834    if (src1->type != GGML_TYPE_F32) {
3835        return false;
3836    }
3837
3838    const int ne00  = src0->ne[0];
3839    const int ne11  = src1->ne[1];
3840    const int ne12  = src1->ne[2];
3841    const int wtype = src0->type;
3842
3843    // HMX weight tile requires N to be 32-aligned.
3844    if (ne01_padded % 32 != 0) {
3845        return false;
3846    }
3847
3848    // HMX supports F16, F32, and repack quantized types.
3849    if (!ggml_hexagon_is_hmx_weight_type((ggml_type) wtype)) {
3850        return false;
3851    }
3852
3853    // HMX paths require K aligned to 32.
3854    if (ne00 % 32 != 0) {
3855        return false;
3856    }
3857
3858    // Quantized HMX kernels only handle flat 2D matmul (or matmul_id wrapping flat 2D matmuls).
3859    if (!is_matmul_id && is_batched && wtype != GGML_TYPE_F16) {
3860        return false;
3861    }
3862
3863    // HMX assumes contiguous row-major layout.
3864    if (src0->nb[0] > src0->nb[1] || src1->nb[0] > src1->nb[1]) {
3865        return false;
3866    }
3867
3868    // M alignment: Use HMX when M > HTP_MM_HMX_MIN_NROWS.
3869    // For MUL_MAT_ID, src1 shape is [K, n_expert_used, n_tokens, 1], so n_tokens is ne12.
3870    const int m = is_matmul_id ? ne12 : ne11;
3871    if (m <= HTP_MM_HMX_MIN_NROWS) {
3872        return false;
3873    }
3874
3875    return true;
3876
3877    GGML_UNUSED(dst);
3878}
3879
3880static bool ggml_hexagon_precompute_hmx_mm_params(
3881    const struct ggml_hexagon_session * sess,
3882    const struct ggml_tensor * src0,
3883    const struct ggml_tensor * src1,
3884    const struct ggml_tensor * dst,
3885    int wtype,
3886    int ne00_padded,
3887    int ne01_padded,
3888    int ne02,
3889    int ne11,
3890    int ne12,
3891    int ne11_padded,
3892    bool is_matmul_id,
3893    bool is_batched,
3894    size_t vtcm_budget,
3895    struct htp_mm_kernel_params * kparams
3896) {
3897    const int aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
3898    const bool pipeline = is_matmul_id ? false : htp_mm_hmx_pipeline(ne11);
3899    const int n_threads = (int)sess->n_threads;
3900    const int ne10 = src1->ne[0];
3901
3902    const bool is_batched_val = is_matmul_id ? false : is_batched;
3903    const int group_size = (ne02 > 0 ? ne12 / ne02 : 1);
3904
3905    size_t m_chunk = 0;
3906    size_t n_chunk = 0;
3907    size_t vtcm_size = 0;
3908    bool use_grouped = false;
3909    int act_threads_selected = 0;
3910
3911    if (is_batched_val && wtype == GGML_TYPE_F16 && group_size > 1) {
3912        // Try grouped path first
3913        const bool use_dma_activation = (src1->nb[1]/sizeof(float) > (size_t)ne00_padded);
3914        if (htp_mm_hmx_solve_batched_params(wtype, ne00_padded, ne01_padded, ne11, group_size, use_dma_activation, n_threads, pipeline, vtcm_budget, &m_chunk, &n_chunk, &act_threads_selected, &vtcm_size)) {
3915            use_grouped = true;
3916        }
3917    }
3918
3919    if (!use_grouped) {
3920        // Fallback to simple 2D path (group_size = 1)
3921        const int m_id_rows = (dst && is_matmul_id) ? (int) ((size_t) dst->ne[1] * dst->ne[2]) : 0;
3922        if (!htp_mm_hmx_solve_2d_params(wtype, ne00_padded, m_id_rows, ne01_padded, ne11_padded, ne11, n_threads, pipeline, is_matmul_id, aligned_tile_size, vtcm_budget, &m_chunk, &n_chunk, &act_threads_selected, &vtcm_size)) {
3923            return false;
3924        }
3925    }
3926
3927    kparams->n_hmx = 1;
3928    kparams->pipeline = pipeline ? 1 : 0;
3929    kparams->m_chunk = m_chunk;
3930    kparams->n_chunk = n_chunk;
3931    kparams->n_threads = n_threads;
3932    kparams->n_act_threads = act_threads_selected;
3933    kparams->tile_size = htp_mm_get_weight_tile_size(wtype);
3934    kparams->aligned_tile_size = aligned_tile_size;
3935    kparams->src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
3936    kparams->vtcm_size = vtcm_size;
3937    kparams->vtcm_src0_size = 0;
3938    kparams->div_n_act_threads = init_fastdiv_values(act_threads_selected);
3939    kparams->div_ne00_padded   = init_fastdiv_values(ne00_padded);
3940    kparams->vtcm_src1_size = 0;
3941    kparams->vtcm_dst_size = 0;
3942
3943    if (is_batched && !is_matmul_id) {
3944        kparams->kernel_type = HTP_MM_KERNEL_HMX_F16_BATCHED;
3945    } else {
3946        kparams->kernel_type = HTP_MM_KERNEL_HMX_2D;
3947    }
3948    return true;
3949
3950    GGML_UNUSED(src0);
3951}
3952
3953static void ggml_hexagon_precompute_hvx_mm_params(
3954    const struct ggml_hexagon_session * sess,
3955    const struct ggml_tensor * src0,
3956    const struct ggml_tensor * src1,
3957    const struct ggml_tensor * dst,
3958    int wtype,
3959    int ne02,
3960    int ne03,
3961    int ne10,
3962    int ne11,
3963    int ne12,
3964    int ne13,
3965    bool is_matmul_id,
3966    const size_t src2_row_size,
3967    size_t vtcm_budget,
3968    struct htp_mm_kernel_params * kparams
3969) {
3970    kparams->n_hmx = 0;
3971    kparams->n_threads = sess->n_threads;
3972
3973    const bool is_quant = (wtype != GGML_TYPE_F16 && wtype != GGML_TYPE_F32);
3974    const int src1_nrows = ne11 * ne12 * ne13;
3975
3976    if (is_quant) {
3977        // Quantized HVX
3978        kparams->tile_size = htp_mm_get_weight_tile_size(wtype);
3979        kparams->aligned_tile_size = htp_mm_get_weight_aligned_tile_size(wtype);
3980
3981        const bool k_align = (ne10 % 32 == 0);
3982
3983        if (is_matmul_id) {
3984            kparams->kernel_type   = (src1_nrows < (int) sess->n_threads) ? HTP_MM_KERNEL_HVX_QUANT_BLOCK : HTP_MM_KERNEL_HVX_QUANT_ROW;
3985            kparams->src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
3986
3987            struct htp_mm_hvx_vtcm_layout L;
3988            uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
3989            uint32_t best_n_prefetch = 2;
3990            for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
3991                htp_mm_hvx_vtcm_layout_build(
3992                    &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
3993                    0, src0->nb[1], 0, src2_row_size, d, true, false
3994                );
3995                if (L.total_bytes <= vtcm_budget) {
3996                    best_n_prefetch = d;
3997                    break;
3998                }
3999            }
4000            if (best_n_prefetch == 2 && L.total_bytes > vtcm_budget) {
4001                htp_mm_hvx_vtcm_layout_build(
4002                    &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
4003                    0, src0->nb[1], 0, src2_row_size, 2, true, false
4004                );
4005            }
4006            kparams->n_prefetch = best_n_prefetch;
4007            kparams->vtcm_size      = L.total_bytes;
4008            kparams->vtcm_src0_size = L.src0_bytes;
4009            kparams->vtcm_src1_size = L.src1_bytes;
4010            kparams->vtcm_dst_size  = L.dst_bytes;
4011        } else {
4012            bool try_tiled = (k_align && opt_mm_select >= 2);
4013            if (try_tiled) {
4014                kparams->src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
4015                if (src1_nrows < (int)sess->n_threads) {
4016                    kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_BLOCK;
4017                } else {
4018                    kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW;
4019                }
4020
4021                struct htp_mm_hvx_vtcm_layout L;
4022                uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
4023                uint32_t best_n_prefetch = 2;
4024                for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
4025                    htp_mm_hvx_vtcm_layout_build(
4026                        &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
4027                        dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, d, false, false
4028                    );
4029                    if (L.total_bytes <= vtcm_budget) {
4030                        best_n_prefetch = d;
4031                        break;
4032                    }
4033                }
4034                if (best_n_prefetch == 2 && L.total_bytes > vtcm_budget) {
4035                    htp_mm_hvx_vtcm_layout_build(
4036                        &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
4037                        dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, 2, false, false
4038                    );
4039                }
4040
4041                kparams->n_prefetch = best_n_prefetch;
4042
4043                if (L.total_bytes <= vtcm_budget) {
4044                    kparams->vtcm_size = L.total_bytes;
4045                    kparams->vtcm_src0_size = L.src0_bytes;
4046                    kparams->vtcm_src1_size = L.src1_bytes;
4047                    kparams->vtcm_dst_size = L.dst_bytes;
4048                    goto done_quant;
4049                }
4050                HEX_VERBOSE("ggml-hex: %s HVX tiled path VTCM size needed (%zu) > budget (%zu), falling back to HVX flat\n", sess->name.c_str(), L.total_bytes, vtcm_budget);
4051            }
4052
4053            // Flat HVX fallback
4054            {
4055                kparams->src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
4056                kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT;
4057
4058                struct htp_mm_hvx_vtcm_layout L;
4059                htp_mm_hvx_vtcm_layout_build(
4060                    &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
4061                    dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, 16, false, false
4062                );
4063
4064                kparams->n_prefetch = 16;
4065                kparams->vtcm_size = L.total_bytes;
4066                kparams->vtcm_src0_size = L.src0_bytes;
4067                kparams->vtcm_src1_size = L.src1_bytes;
4068                kparams->vtcm_dst_size = L.dst_bytes;
4069            }
4070        }
4071
4072    done_quant:;
4073    } else if (wtype == GGML_TYPE_F16) {
4074        // F16 HVX
4075        const bool is_batched  = (ne02 > 1) || (ne03 > 1);
4076        const bool is_permuted = ggml_is_permuted(src0) || ggml_is_permuted(src1);
4077
4078        struct htp_mm_hvx_vtcm_layout L;
4079        htp_mm_hvx_vtcm_layout_build(
4080            &L, HTP_MM_KERNEL_HVX_F16_F16_VTCM, wtype, ne10, src1_nrows, sess->n_threads,
4081            dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, 16, false, false
4082        );
4083
4084        if (!is_batched && !is_permuted && L.total_bytes <= vtcm_budget) {
4085            kparams->kernel_type = HTP_MM_KERNEL_HVX_F16_F16_VTCM;
4086            kparams->src1_row_size = hex_round_up(ne10 * 2, 128);
4087            kparams->vtcm_size = L.total_bytes;
4088            kparams->vtcm_src0_size = L.src0_bytes;
4089            kparams->vtcm_src1_size = L.src1_bytes;
4090            kparams->vtcm_dst_size = L.dst_bytes;
4091            kparams->n_prefetch = 16;
4092        } else {
4093            if (src1->type == GGML_TYPE_F32) {
4094                kparams->kernel_type = HTP_MM_KERNEL_HVX_F16_F32_DDR;
4095            } else {
4096                kparams->kernel_type = HTP_MM_KERNEL_HVX_F16_F16_DDR;
4097            }
4098            kparams->src1_row_size = src1->nb[1];
4099            htp_mm_hvx_vtcm_layout_build(
4100                &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
4101                dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, 16, false, false
4102            );
4103            kparams->vtcm_size = L.total_bytes;
4104            kparams->vtcm_src0_size = L.src0_bytes;
4105            kparams->vtcm_src1_size = L.src1_bytes;
4106            kparams->vtcm_dst_size = L.dst_bytes;
4107            kparams->n_prefetch = 16;
4108        }
4109    } else {
4110        // F32 HVX
4111        const bool is_batched  = (ne02 > 1) || (ne03 > 1);
4112        const bool is_permuted = ggml_is_permuted(src0) || ggml_is_permuted(src1);
4113
4114        struct htp_mm_hvx_vtcm_layout L;
4115        htp_mm_hvx_vtcm_layout_build(
4116            &L, HTP_MM_KERNEL_HVX_F32_F32_VTCM, wtype, ne10, src1_nrows, sess->n_threads,
4117            dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, 16, false, false
4118        );
4119
4120        if (!is_batched && !is_permuted && L.total_bytes <= vtcm_budget) {
4121            kparams->kernel_type = HTP_MM_KERNEL_HVX_F32_F32_VTCM;
4122            kparams->src1_row_size = hex_round_up(ne10 * 4, 128);
4123            kparams->vtcm_size = L.total_bytes;
4124            kparams->vtcm_src0_size = L.src0_bytes;
4125            kparams->vtcm_src1_size = L.src1_bytes;
4126            kparams->vtcm_dst_size = L.dst_bytes;
4127            kparams->n_prefetch = 16;
4128        } else {
4129            kparams->kernel_type = HTP_MM_KERNEL_HVX_F32_F32_DDR;
4130            kparams->src1_row_size = src1->nb[1];
4131            htp_mm_hvx_vtcm_layout_build(
4132                &L, kparams->kernel_type, wtype, ne10, src1_nrows, sess->n_threads,
4133                dst->nb[1], src0->nb[1], src1->nb[1], src2_row_size, 16, false, false
4134            );
4135            kparams->vtcm_size = L.total_bytes;
4136            kparams->vtcm_src0_size = L.src0_bytes;
4137            kparams->vtcm_src1_size = L.src1_bytes;
4138            kparams->vtcm_dst_size = L.dst_bytes;
4139            kparams->n_prefetch = 16;
4140        }
4141    }
4142}
4143
4144static void ggml_hexagon_precompute_matmul_params_impl(
4145    const struct ggml_hexagon_session * sess,
4146    const struct ggml_tensor * src0,
4147    const struct ggml_tensor * src1,
4148    const struct ggml_tensor * dst,
4149    const size_t src2_row_size,
4150    struct htp_mm_kernel_params * kparams
4151) {
4152    memset(kparams, 0, sizeof(*kparams));
4153
4154    const int ne00 = src0->ne[0];
4155    const int ne01 = src0->ne[1];
4156    const int ne02 = src0->ne[2];
4157    const int ne03 = src0->ne[3];
4158
4159    const int ne10 = src1->ne[0];
4160    const int ne11 = src1->ne[1];
4161    const int ne12 = src1->ne[2];
4162    const int ne13 = src1->ne[3];
4163
4164    const int wtype = src0->type;
4165    const bool is_repack = ggml_hexagon_is_repack_type((ggml_type) wtype);
4166    const int ne00_padded = is_repack ? hex_round_up(ne00, 32) : ne00;
4167    const int ne01_padded = is_repack ? hex_round_up(ne01, 32) : ne01;
4168    const int ne11_padded = hex_round_up(ne11, 32);
4169
4170    const bool is_matmul_id = (dst->op == GGML_OP_MUL_MAT_ID);
4171    const bool is_batched   = (ne02 * ne03 > 1 || ne12 * ne13 > 1);
4172
4173    const size_t vtcm_budget = sess->vtcm_size;
4174
4175    // Check HMX eligibility and try precomputing HMX parameters
4176    bool hmx_enabled = (sess->n_hmx > 0) && (opt_mm_select >= 3);
4177    if (hmx_enabled && ggml_hexagon_matmul_is_hmx_eligible(src0, src1, dst, ne01_padded, is_matmul_id, is_batched)) {
4178        if (ggml_hexagon_precompute_hmx_mm_params(sess, src0, src1, dst, wtype, ne00_padded, ne01_padded, ne02, ne11, ne12, ne11_padded, is_matmul_id, is_batched, vtcm_budget, kparams)) {
4179            goto finalize;
4180        }
4181    }
4182
4183    // Fallback to HVX parameter computation
4184    ggml_hexagon_precompute_hvx_mm_params(sess, src0, src1, dst, wtype, ne02, ne03, ne10, ne11, ne12, ne13, is_matmul_id, src2_row_size, vtcm_budget, kparams);
4185
4186finalize:
4187    kparams->div_ne12_ne1 = init_fastdiv_values(ne12 * ne11);
4188    kparams->div_ne1      = init_fastdiv_values(ne11);
4189    kparams->div_r2       = init_fastdiv_values(ne02 > 0 ? ne12 / ne02 : 1);
4190    kparams->div_r3       = init_fastdiv_values(ne03 > 0 ? ne13 / ne03 : 1);
4191    kparams->div_ne11     = init_fastdiv_values(ne11);
4192}
4193
4194static void ggml_hexagon_precompute_matmul_params(
4195    const struct ggml_hexagon_session * sess,
4196    const struct ggml_tensor * src0,
4197    const struct ggml_tensor * src1,
4198    const struct ggml_tensor * dst,
4199    struct htp_mm_kernel_params * kparams
4200) {
4201    ggml_hexagon_precompute_matmul_params_impl(sess, src0, src1, dst, 0, kparams);
4202}
4203
4204static void ggml_hexagon_precompute_fused_matmul_add_params(
4205    const struct ggml_hexagon_session * sess,
4206    const struct ggml_tensor * src0,
4207    const struct ggml_tensor * src1,
4208    const struct ggml_tensor * src2,
4209    const struct ggml_tensor * dst,
4210    struct htp_mm_kernel_params * kparams
4211) {
4212    ggml_hexagon_precompute_matmul_params_impl(sess, src0, src1, dst, src2->nb[1], kparams);
4213}
4214
4215static void ggml_hexagon_precompute_unary_params(
4216    const struct ggml_hexagon_session * sess,
4217    uint32_t op,
4218    const struct ggml_tensor * src0,
4219    const struct ggml_tensor * src1,
4220    const struct ggml_tensor * dst,
4221    struct htp_unary_kernel_params * kparams
4222) {
4223    memset(kparams, 0, sizeof(*kparams));
4224
4225    const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
4226    const uint32_t n_threads  = (std::min)((uint32_t)sess->n_threads, src0_nrows);
4227
4228    kparams->n_threads = n_threads;
4229
4230    const size_t elem_size = ggml_type_size(src0->type);
4231
4232    const size_t src0_data_row_size = src0->ne[0] * elem_size;
4233    const size_t dst_data_row_size  = dst->ne[0]  * ggml_type_size(dst->type);
4234
4235    const size_t src0_row_size_aligned = hex_round_up(src0_data_row_size, 128);
4236    const size_t dst_row_size_aligned  = hex_round_up(dst_data_row_size,  128);
4237
4238    kparams->src0_row_size_aligned = src0_row_size_aligned;
4239    kparams->dst_row_size_aligned  = dst_row_size_aligned;
4240
4241    size_t src1_data_row_size = 0;
4242    size_t src1_row_size_aligned = 0;
4243    bool broadcast_weight = false;
4244
4245    if (op == HTP_OP_RMS_NORM_MUL) {
4246        GGML_ASSERT(src1 != nullptr);
4247        src1_data_row_size = src1->ne[0] * ggml_type_size(src1->type);
4248        src1_row_size_aligned = hex_round_up(src1_data_row_size, 128);
4249        broadcast_weight = (src1->ne[1] * src1->ne[2] * src1->ne[3] == 1);
4250    }
4251
4252    kparams->src1_row_size_aligned = src1_row_size_aligned;
4253    kparams->broadcast_weight      = broadcast_weight;
4254
4255    struct htp_unary_vtcm_layout L;
4256    uint32_t col_tile = 0;
4257    uint32_t vtcm_row_per_thread = 0;
4258
4259    htp_unary_vtcm_layout_build(&L, op, src0->ne[0], dst->ne[0],
4260                                op == HTP_OP_RMS_NORM_MUL ? src1->ne[0] : 0,
4261                                broadcast_weight, n_threads, sess->vtcm_size, elem_size,
4262                                &col_tile, &vtcm_row_per_thread);
4263
4264    kparams->col_tile = col_tile;
4265    kparams->vtcm_row_per_thread = vtcm_row_per_thread;
4266    kparams->vtcm_size = L.total_bytes;
4267
4268    kparams->vtcm_src0_size_per_thread = L.src0_bytes;
4269    kparams->vtcm_src1_size_per_thread = L.src1_bytes;
4270    kparams->vtcm_dst_size_per_thread  = L.dst_bytes;
4271
4272    kparams->vtcm_src0_size = L.src0_bytes * n_threads;
4273    kparams->vtcm_src1_size = L.src1_bytes * n_threads;
4274    kparams->vtcm_dst_size  = L.dst_bytes * n_threads;
4275
4276    kparams->block = col_tile ? 0 : ((L.src0_bytes / 2) / src0_row_size_aligned);
4277
4278    const uint32_t tiles_per_row = col_tile > 0 ? (src0->ne[0] + col_tile - 1) / col_tile : 1;
4279    kparams->div_ne01  = init_fastdiv_values(src0->ne[1]);
4280    kparams->div_ne02  = init_fastdiv_values(src0->ne[2]);
4281    kparams->div_ne012 = init_fastdiv_values(src0->ne[1] * src0->ne[2]);
4282    kparams->div_tpr   = init_fastdiv_values(tiles_per_row);
4283}
4284
4285static void ggml_hexagon_precompute_get_rows_params(
4286    const struct ggml_hexagon_session * sess,
4287    const struct ggml_tensor * src0,
4288    const struct ggml_tensor * src1,
4289    const struct ggml_tensor * dst,
4290    struct htp_get_rows_kernel_params * kparams
4291) {
4292    memset(kparams, 0, sizeof(*kparams));
4293
4294    const uint32_t ne00 = src0->ne[0];
4295    const uint32_t ne02 = src0->ne[2];
4296    const uint32_t ne03 = src0->ne[3];
4297
4298    const uint32_t ne10 = src1->ne[0];
4299    const uint32_t ne11 = src1->ne[1];
4300    const uint32_t ne12 = src1->ne[2];
4301    const uint32_t nr = ne10 * ne11 * ne12;
4302
4303    const size_t nb01 = src0->nb[1];
4304    const size_t nb1 = dst->nb[1];
4305
4306    const bool can_use_dma = (src0->type == dst->type) && (nb01 == nb1);
4307    const bool use_dma = can_use_dma && (ne00 >= 2048);
4308
4309    kparams->use_dma = use_dma ? 1 : 0;
4310
4311    uint32_t chunks_per_row = 1;
4312    uint32_t chunk_size = ne00;
4313    uint32_t total_tasks = nr;
4314
4315    if (use_dma) {
4316        kparams->n_threads = (std::min)((uint32_t)sess->n_threads, nr);
4317        kparams->tasks_per_thread = (nr + kparams->n_threads - 1) / kparams->n_threads;
4318    } else {
4319        if (src0->type == GGML_TYPE_F32 && nr < sess->n_threads) {
4320            const uint32_t min_chunk_size = 1024;
4321            uint32_t max_chunks = ne00 / min_chunk_size;
4322            if (max_chunks == 0) {
4323                max_chunks = 1;
4324            }
4325            chunks_per_row = (std::min)((sess->n_threads + nr - 1) / nr, max_chunks);
4326            chunk_size = (ne00 + chunks_per_row - 1) / chunks_per_row;
4327            total_tasks = nr * chunks_per_row;
4328        }
4329        kparams->n_threads = (std::min)(total_tasks, (uint32_t)sess->n_threads);
4330        kparams->tasks_per_thread = (total_tasks + kparams->n_threads - 1) / kparams->n_threads;
4331    }
4332
4333    kparams->chunks_per_row = chunks_per_row;
4334    kparams->chunk_size = chunk_size;
4335    kparams->total_tasks = total_tasks;
4336
4337    kparams->div_ne10 = init_fastdiv_values(ne10);
4338    kparams->div_ne10_ne11 = init_fastdiv_values(ne10 * ne11);
4339    kparams->div_chunks_per_row = init_fastdiv_values(chunks_per_row);
4340    kparams->div_ne02 = init_fastdiv_values(ne02);
4341    kparams->div_ne03 = init_fastdiv_values(ne03);
4342
4343    struct htp_get_rows_vtcm_layout vtcm_layout;
4344    htp_get_rows_vtcm_layout_build(&vtcm_layout, src0->type, ne00, kparams->n_threads);
4345    kparams->vtcm_size = vtcm_layout.total_bytes;
4346}
4347
4348static void ggml_hexagon_precompute_set_rows_params(
4349    const struct ggml_hexagon_session * sess,
4350    const struct ggml_tensor * src0, // values
4351    const struct ggml_tensor * src1, // indices
4352    const struct ggml_tensor * dst,  // destination
4353    struct htp_set_rows_kernel_params * kparams
4354) {
4355    memset(kparams, 0, sizeof(*kparams));
4356
4357    const uint32_t nr = src0->ne[1];
4358
4359    kparams->n_threads = (std::min)((uint32_t)sess->n_threads, nr);
4360    kparams->tasks_per_thread = (nr + kparams->n_threads - 1) / kparams->n_threads;
4361    kparams->total_tasks = nr;
4362
4363    kparams->div_ne11 = init_fastdiv_values(src1->ne[1]);
4364    kparams->div_ne12 = init_fastdiv_values(src1->ne[2]);
4365    kparams->div_tasks_per_thread = init_fastdiv_values(kparams->tasks_per_thread);
4366    kparams->div_ne02 = init_fastdiv_values(src0->ne[2]);
4367
4368    struct htp_set_rows_vtcm_layout vtcm_layout;
4369    htp_set_rows_vtcm_layout_build(&vtcm_layout, dst->type, src0->ne[0], kparams->n_threads);
4370    kparams->vtcm_size = vtcm_layout.total_bytes;
4371}
4372
4373static void ggml_hexagon_precompute_rope_params(
4374    const struct ggml_hexagon_session * sess,
4375    const struct ggml_tensor * op,
4376    struct htp_rope_kernel_params * kparams
4377) {
4378    memset(kparams, 0, sizeof(*kparams));
4379
4380    const struct ggml_tensor * src0 = op->src[0];
4381    const struct ggml_tensor * dst  = op;
4382
4383    const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
4384    const uint32_t n_threads  = (std::min)((uint32_t) sess->n_threads, src0_nrows);
4385
4386    struct htp_rope_vtcm_layout layout;
4387    htp_rope_vtcm_layout_build(&layout, src0->ne[0], n_threads);
4388
4389    kparams->n_threads              = n_threads;
4390    kparams->src0_nrows             = src0_nrows;
4391    kparams->src0_nrows_per_thread  = (src0_nrows + n_threads - 1) / n_threads;
4392    kparams->vtcm_size              = (uint32_t) layout.total_bytes;
4393    kparams->spad_per_thread        = (uint32_t) layout.bytes_per_thread;
4394    kparams->theta_cache_offset     = (uint32_t) layout.theta_cache_size_aligned;
4395    kparams->src0_row_size_aligned  = (uint32_t) layout.src0_row_size_aligned;
4396
4397    if (src0_nrows > 0) {
4398        kparams->div_ne2_ne1 = init_fastdiv_values(dst->ne[2] * dst->ne[1]);
4399        kparams->div_ne1     = init_fastdiv_values(dst->ne[1]);
4400    }
4401}
4402
4403static void ggml_hexagon_precompute_fused_mmnx_params(
4404    const struct ggml_hexagon_session * sess,
4405    const struct ggml_tensor * src0, // W0
4406    const struct ggml_tensor * src1, // x
4407    int32_t n_weights,
4408    struct htp_mm_kernel_params * kparams
4409) {
4410    memset(kparams, 0, sizeof(*kparams));
4411    kparams->n_threads = sess->n_threads;
4412
4413    const int ne00 = src0->ne[0];
4414    const int ne01 = src0->ne[1];
4415    const int ne02 = src0->ne[2];
4416    const int ne03 = src0->ne[3];
4417
4418    const int ne10 = src1->ne[0];
4419    const int ne11 = src1->ne[1];
4420    const int ne12 = src1->ne[2];
4421    const int ne13 = src1->ne[3];
4422
4423    const int wtype = src0->type;
4424    const bool is_repack = ggml_hexagon_is_repack_type((ggml_type) wtype);
4425    const int ne00_padded = is_repack ? hex_round_up(ne00, 32) : ne00;
4426    const int ne01_padded = is_repack ? hex_round_up(ne01, 32) : ne01;
4427    const int ne11_padded = hex_round_up(ne11, 32);
4428
4429    const size_t vtcm_budget = sess->vtcm_size;
4430    const bool is_batched = (ne02 * ne03 > 1 || ne12 * ne13 > 1);
4431
4432    bool hmx_enabled = (sess->n_hmx > 0) && (opt_mm_select >= 3);
4433    if (hmx_enabled && ggml_hexagon_matmul_is_hmx_eligible(src0, src1, nullptr, ne01_padded, false, is_batched)) {
4434        if (ggml_hexagon_precompute_hmx_mm_params(sess, src0, src1, nullptr, wtype, ne00_padded, ne01_padded, ne02, ne11, ne12, ne11_padded, false, is_batched, vtcm_budget, kparams)) {
4435            kparams->n_weights = n_weights;
4436            goto finalize;
4437        }
4438    }
4439
4440    if (!is_repack) {
4441        kparams->kernel_type = HTP_MM_KERNEL_UNSUPPORTED;
4442        return;
4443    }
4444
4445    {
4446        const int src1_nrows = ne11 * ne12 * ne13;
4447        const size_t src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_tiled_row_size(ne10) : htp_mm_q8_0_tiled_row_size(ne10);
4448        const size_t src0_row_size = src0->nb[1];
4449
4450        uint32_t best_n_prefetch = 16;
4451
4452        if (is_repack) {
4453            const uint32_t max_prefetch = (src1_nrows > HTP_MM_HMX_MIN_NROWS) ? 2 : 16;
4454            best_n_prefetch = 2;
4455            for (uint32_t d = max_prefetch; d >= 2; d /= 2) {
4456                struct htp_mm_hvx_vtcm_layout L;
4457                htp_mm_hvx_vtcm_layout_build(
4458                    &L, HTP_MM_KERNEL_HVX_QUANT_ROW, wtype, ne10, src1_nrows, sess->n_threads,
4459                    0, src0_row_size, src1_row_size, 0, d, false, true
4460                );
4461                if (L.total_bytes <= sess->vtcm_size) {
4462                    best_n_prefetch = d;
4463                    break;
4464                }
4465            }
4466        }
4467
4468        struct htp_mm_hvx_vtcm_layout L;
4469        bool try_tiled = (opt_mm_select >= 2);
4470
4471        // Test tiled first
4472        htp_mm_hvx_vtcm_layout_build(
4473            &L, HTP_MM_KERNEL_HVX_QUANT_ROW, wtype, ne10, src1_nrows, sess->n_threads,
4474            0, src0_row_size, src1_row_size, 0, best_n_prefetch, false, true
4475        );
4476
4477        if (try_tiled && L.total_bytes <= sess->vtcm_size) {
4478            kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW;
4479            kparams->vtcm_src0_size = L.src0_bytes;
4480            kparams->vtcm_src1_size = L.src1_bytes;
4481            kparams->vtcm_dst_size  = L.dst_bytes;
4482            kparams->vtcm_size      = L.total_bytes;
4483            kparams->n_prefetch     = best_n_prefetch;
4484            kparams->n_weights      = n_weights;
4485        } else {
4486            kparams->kernel_type = HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT;
4487            size_t flat_src1_row_size = (wtype == GGML_TYPE_Q4_1) ? htp_mm_q8_1_flat_row_size(ne10) : htp_mm_q8_0_flat_row_size(ne10);
4488
4489            htp_mm_hvx_vtcm_layout_build(
4490                &L, HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT, wtype, ne10, src1_nrows, sess->n_threads,
4491                0, src0_row_size, flat_src1_row_size, 0, best_n_prefetch, false, true
4492            );
4493            kparams->vtcm_src0_size = L.src0_bytes;
4494            kparams->vtcm_src1_size = L.src1_bytes;
4495            kparams->vtcm_dst_size  = L.dst_bytes;
4496            kparams->vtcm_size      = L.total_bytes;
4497            kparams->n_prefetch     = best_n_prefetch;
4498            kparams->n_weights      = n_weights;
4499        }
4500    }
4501
4502finalize:
4503    kparams->div_ne12_ne1 = init_fastdiv_values(ne12 * ne11);
4504    kparams->div_ne1      = init_fastdiv_values(ne11);
4505    kparams->div_r2       = init_fastdiv_values(ne02 > 0 ? ne12 / ne02 : 1);
4506    kparams->div_r3       = init_fastdiv_values(ne03 > 0 ? ne13 / ne03 : 1);
4507    kparams->div_ne11     = init_fastdiv_values(ne11);
4508}
4509
4510static void ggml_hexagon_precompute_fused_mmidnx_params(
4511    const struct ggml_hexagon_session * sess,
4512    const struct ggml_tensor * src0, // W0
4513    const struct ggml_tensor * src1, // x
4514    const struct ggml_tensor * dst,  // dst0
4515    int32_t n_weights,
4516    struct htp_mm_kernel_params * kparams
4517) {
4518    ggml_hexagon_precompute_matmul_params_impl(sess, src0, src1, dst, 0, kparams);
4519    kparams->n_weights = n_weights;
4520}
4521
4522static bool ggml_hexagon_tensor_is_host(const struct ggml_hexagon_session * sess, const struct ggml_tensor * t) {
4523    return t && t->buffer && ggml_backend_buft_is_host(t->buffer->buft);
4524    GGML_UNUSED(sess);
4525}
4526
4527static bool ggml_hexagon_tensor_is_non_host(const struct ggml_hexagon_session * sess, const struct ggml_tensor * t) {
4528    return t && t->buffer && !ggml_backend_buft_is_host(t->buffer->buft);
4529    GGML_UNUSED(sess);
4530}
4531
4532static bool ggml_hexagon_supported_mul_mat(const struct ggml_hexagon_session * sess, const struct ggml_tensor * dst) {
4533    const struct ggml_tensor * src0 = dst->src[0];
4534    const struct ggml_tensor * src1 = dst->src[1];
4535
4536    if (dst->type != GGML_TYPE_F32) {
4537        return false;
4538    }
4539
4540    if (src1->type != GGML_TYPE_F32 && src1->type != GGML_TYPE_F16) {
4541        return false;
4542    }
4543
4544    switch (src0->type) {
4545        case GGML_TYPE_Q4_0:
4546        case GGML_TYPE_Q4_1:
4547        case GGML_TYPE_Q8_0:
4548        case GGML_TYPE_IQ4_NL:
4549        case GGML_TYPE_MXFP4:
4550            if (src0->ne[0] % 32) {
4551                return false;
4552            }
4553
4554            if (src1->ne[2] != 1 || src1->ne[3] != 1) {
4555                return false;  // no broadcasting (for now)
4556            }
4557
4558            if (!src0->buffer) {
4559                sess->needs_repack.insert(src0);
4560            }
4561            break;
4562
4563        case GGML_TYPE_F16:
4564            if (src0->nb[1] < src0->nb[0]) {
4565                return false;
4566            }
4567            if (src1->ne[2] < src0->ne[2] || src1->ne[3] < src0->ne[3]) {
4568                return false;
4569            }
4570            break;
4571
4572        case GGML_TYPE_F32:
4573            if (src1->type != GGML_TYPE_F32) {
4574                return false;
4575            }
4576            if (src0->nb[1] < src0->nb[0]) {
4577                return false;
4578            }
4579            if (src1->ne[2] < src0->ne[2] || src1->ne[3] < src0->ne[3]) {
4580                return false;
4581            }
4582            break;
4583
4584        default:
4585            return false;
4586    }
4587
4588    struct htp_mm_kernel_params kparams;
4589    ggml_hexagon_precompute_matmul_params(sess, src0, src1, dst, &kparams);
4590    if ((size_t)kparams.vtcm_size > sess->vtcm_size) {
4591        HEX_VERBOSE("ggml-hex: %s supported MUL_MAT VTCM size needed (%d) > budget (%zu)\n", sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
4592        return false;
4593    }
4594
4595    return true;
4596}
4597
4598static bool ggml_hexagon_supported_mul_mat_id(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4599    const struct ggml_tensor * src0 = op->src[0];
4600    const struct ggml_tensor * src1 = op->src[1];
4601    const struct ggml_tensor * src2 = op->src[2];
4602    const struct ggml_tensor * dst  = op;
4603
4604    if (src1->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32 || src2->type != GGML_TYPE_I32) {
4605        return false;
4606    }
4607
4608    switch (src0->type) {
4609        case GGML_TYPE_Q4_0:
4610        case GGML_TYPE_Q4_1:
4611        case GGML_TYPE_Q8_0:
4612        case GGML_TYPE_IQ4_NL:
4613        case GGML_TYPE_MXFP4:
4614            if ((src0->ne[0] % 32)) {
4615                return false;
4616            }
4617
4618            if (!src0->buffer) {
4619                sess->needs_repack.insert(src0);
4620            }
4621            break;
4622
4623        default:
4624            return false;
4625    }
4626
4627    struct htp_mm_kernel_params kparams;
4628    ggml_hexagon_precompute_matmul_params(sess, src0, src1, dst, &kparams);
4629    if ((size_t)kparams.vtcm_size > sess->vtcm_size) {
4630        HEX_VERBOSE("ggml-hex: %s supported MUL_MAT_ID VTCM size needed (%d) > budget (%zu)\n", sess->c_name(), kparams.vtcm_size, sess->vtcm_size);
4631        return false;
4632    }
4633
4634    return true;
4635}
4636
4637static bool ggml_hexagon_supported_binary(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4638    const struct ggml_tensor * src0 = op->src[0];
4639    const struct ggml_tensor * src1 = op->src[1];
4640    const struct ggml_tensor * dst  = op;
4641
4642    if (src0->type == GGML_TYPE_F32) {
4643        if (src1->type != GGML_TYPE_F32) {
4644            return false;
4645        }
4646        if (dst->type != GGML_TYPE_F32) {
4647            return false;
4648        }
4649    }
4650    else if (src0->type == GGML_TYPE_F16) {
4651        if (src1->type != GGML_TYPE_F16) {
4652            return false;
4653        }
4654        if (dst->type != GGML_TYPE_F16) {
4655            return false;
4656        }
4657    }
4658    else {
4659        return false;
4660    }
4661
4662    if (ggml_is_permuted(src0) || ggml_is_permuted(dst)) {
4663        return false;
4664    }
4665    if (!ggml_are_same_shape(src0, dst)) {
4666        return false;
4667    }
4668    if (!ggml_can_repeat(src1, src0) || ggml_is_permuted(src1)) {
4669        return false;
4670    }
4671
4672    return true;
4673
4674    GGML_UNUSED(sess);
4675}
4676
4677static bool ggml_hexagon_supported_add_id(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4678    const struct ggml_tensor * src0 = op->src[0];
4679    const struct ggml_tensor * src1 = op->src[1];
4680    const struct ggml_tensor * dst  = op;
4681
4682    if (src0->type != GGML_TYPE_F32) {
4683        return false;
4684    }
4685    if (src1->type != GGML_TYPE_F32) {
4686        return false;
4687    }
4688    if (dst->type != GGML_TYPE_F32) {
4689        return false;
4690    }
4691    if (!ggml_are_same_shape(src0, dst)) {
4692        return false;
4693    }
4694
4695    // REVISIT: add support for non-contigiuos tensors
4696    if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(src1) || !ggml_is_contiguous(dst)) {
4697        return false;
4698    }
4699
4700    return true;
4701
4702    GGML_UNUSED(sess);
4703}
4704
4705static bool ggml_hexagon_supported_unary(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4706    const struct ggml_tensor * src0 = op->src[0];
4707    const struct ggml_tensor * dst  = op;
4708
4709    if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) {
4710        return false;
4711    }
4712    if (dst->type != src0->type) {
4713        return false;
4714    }
4715    if (!ggml_is_contiguous_rows(src0)) {
4716        return false;
4717    }
4718
4719    // F16 device kernels only cover this explicit whitelist (must stay in sync with
4720    // the is_f16 whitelist in execute_op_unary(), unary-ops.c).
4721    if (src0->type == GGML_TYPE_F16) {
4722        switch (op->op) {
4723            case GGML_OP_NORM:
4724            case GGML_OP_RMS_NORM:
4725            case GGML_OP_L2_NORM:
4726            case GGML_OP_SCALE:
4727            case GGML_OP_CLAMP:
4728            case GGML_OP_SQR:
4729            case GGML_OP_SQRT:
4730            case GGML_OP_LOG:
4731                break;
4732            case GGML_OP_UNARY:
4733                if (ggml_get_unary_op(op) != GGML_UNARY_OP_ABS) {
4734                    return false;
4735                }
4736                break;
4737            default:
4738                return false;
4739        }
4740    }
4741
4742    if (!ggml_are_same_shape(src0, dst)) {
4743        return false;
4744    }
4745
4746    // dst must be contiguous; src0 may be non-contiguous
4747    if (!ggml_is_contiguous(dst)) {
4748        return false;
4749    }
4750
4751    return true;
4752
4753    GGML_UNUSED(sess);
4754}
4755
4756static bool ggml_hexagon_supported_sum_rows(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4757    const struct ggml_tensor * src0 = op->src[0];
4758    const struct ggml_tensor * dst  = op;
4759
4760    if (src0->type != GGML_TYPE_F32) {
4761        return false;
4762    }
4763    if (dst->type != GGML_TYPE_F32) {
4764        return false;
4765    }
4766
4767    // TODO: add support for non-contigiuos tensors
4768    if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(dst)) {
4769        return false;
4770    }
4771
4772    return true;
4773
4774    GGML_UNUSED(sess);
4775}
4776
4777static bool ggml_hexagon_supported_activations(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4778    const struct ggml_tensor * src0 = op->src[0];
4779    const struct ggml_tensor * src1 = op->src[1];
4780    const struct ggml_tensor * dst  = op;
4781
4782    if (src0->type != GGML_TYPE_F32) {
4783        return false;
4784    }
4785    if (dst->type != GGML_TYPE_F32) {
4786        return false;
4787    }
4788
4789    if (!ggml_is_contiguous_1(src0)) {
4790        return false;
4791    }
4792    if (!ggml_is_contiguous(dst)) {
4793        return false;
4794    }
4795
4796    if (src1) {
4797        if (src1->type != GGML_TYPE_F32) {
4798            return false;
4799        }
4800        if (!ggml_are_same_shape(src0, src1)) {
4801            return false;
4802        }
4803        if (!ggml_is_contiguous_1(src1)) {
4804            return false;
4805        }
4806    }
4807
4808    return true;
4809
4810    GGML_UNUSED(sess);
4811}
4812
4813static bool ggml_hexagon_supported_softmax(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4814    const struct ggml_tensor * src0 = op->src[0];
4815    const struct ggml_tensor * src1 = op->src[1];
4816    const struct ggml_tensor * src2 = op->src[2];
4817    const struct ggml_tensor * dst  = op;
4818
4819    if (src2) {
4820        return false;  // FIXME: add support for sinks
4821    }
4822
4823    if (src0->type != GGML_TYPE_F32) {
4824        return false;
4825    }
4826    if (dst->type != GGML_TYPE_F32) {
4827        return false;
4828    }
4829
4830    if (src1) {
4831        if (src1->type != GGML_TYPE_F32 && src1->type != GGML_TYPE_F16) {
4832            return false;
4833        }
4834        if (src0->ne[0] != src1->ne[0]) {
4835            return false;
4836        }
4837        if (src1->ne[1] < src0->ne[1]) {
4838            return false;
4839        }
4840        if (src0->ne[2] % src1->ne[2] != 0) {
4841            return false;
4842        }
4843        if (src0->ne[3] % src1->ne[3] != 0) {
4844            return false;
4845        }
4846    }
4847
4848    if (src1) {
4849        if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(src1) || !ggml_is_contiguous(dst)) {
4850            return false;
4851        }
4852    } else {
4853        if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(dst)) {
4854            return false;
4855        }
4856    }
4857
4858    // Reject non-HVX-aligned sizes when ne[0] > HVX_F32_LANES
4859    // The HVX softmax implementation has issues with tail handling for larger non-aligned sizes
4860    // Small sizes (ne[0] <= 32) work correctly with tail-only processing
4861    const int64_t ne0 = src0->ne[0];
4862    if (ne0 > 32 && (ne0 & (32 - 1)) != 0) {
4863        return false;
4864    }
4865
4866    // HVX vector size constraints for softmax
4867    #define SOFTMAX_MAX_ROW_SIZE 131072  // 128K elements max for numerical precision
4868
4869    // Reject very large row sizes to avoid numerical precision issues
4870    // Softmax accumulation over many elements can lead to precision loss
4871    if (ne0 > SOFTMAX_MAX_ROW_SIZE) {
4872        return false;
4873    }
4874
4875    return true;
4876
4877    GGML_UNUSED(sess);
4878}
4879
4880static bool ggml_hexagon_supported_set_rows(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4881    const struct ggml_tensor * src0 = op->src[0]; // values
4882    const struct ggml_tensor * src1 = op->src[1]; // indices
4883    const struct ggml_tensor * dst  = op->src[2] ? op->src[2] : op;
4884
4885    if (dst->type == GGML_TYPE_Q8_0 && src0->ne[0] < 32) {
4886        return false;
4887    }
4888
4889    if (src0->type != GGML_TYPE_F32) {
4890        return false;
4891    }
4892
4893    if (src1->type != GGML_TYPE_I32 && src1->type != GGML_TYPE_I64) {
4894        return false;
4895    }
4896
4897    if (dst->type != GGML_TYPE_F32 && dst->type != GGML_TYPE_F16 && dst->type != GGML_TYPE_Q8_0) {
4898        return false;
4899    }
4900
4901    return true;
4902
4903    GGML_UNUSED(sess);
4904}
4905
4906static bool ggml_hexagon_supported_get_rows(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4907    const struct ggml_tensor * src0 = op->src[0]; // values
4908    const struct ggml_tensor * src1 = op->src[1]; // indices
4909    const struct ggml_tensor * dst  = op;
4910
4911    if (src0->extra) {
4912        const auto * extra = (const ggml_hexagon_tensor_extra *) src0->extra;
4913        if (extra->flags & GGML_HEXAGON_TENSOR_REPACK) {
4914            return false;
4915        }
4916    }
4917
4918    if (src0->type != GGML_TYPE_F32 && src0->ne[0] < 32) {
4919        return false;
4920    }
4921
4922    if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16 && src0->type != GGML_TYPE_Q8_0) {
4923        return false;
4924    }
4925
4926    if (src1->type != GGML_TYPE_I32 && src1->type != GGML_TYPE_I64) {
4927        return false;
4928    }
4929
4930    if (dst->type != GGML_TYPE_F32) {
4931        return false;
4932    }
4933
4934    return true;
4935
4936    GGML_UNUSED(sess);
4937}
4938
4939static bool ggml_hexagon_supported_argsort(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4940    const struct ggml_tensor * src0 = op->src[0]; // values
4941    const struct ggml_tensor * dst  = op;         // indices
4942
4943    if (src0->type != GGML_TYPE_F32) {
4944        return false;
4945    }
4946
4947    if (dst->type != GGML_TYPE_I32) {
4948        return false;
4949    }
4950
4951    if (src0->ne[0] > (16*1024)) {
4952        // reject tensors with huge rows for now
4953        return false;
4954    }
4955
4956    return true;
4957
4958    GGML_UNUSED(sess);
4959}
4960
4961static bool ggml_hexagon_supported_rope(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
4962    const struct ggml_tensor * src0 = op->src[0];
4963    const struct ggml_tensor * src1 = op->src[1];
4964    const struct ggml_tensor * src2 = op->src[2];
4965    const struct ggml_tensor * dst  = op;
4966
4967    if (!ggml_are_same_shape(src0, dst)) {
4968        return false;
4969    }
4970
4971    if (src0->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32 || src1->type != GGML_TYPE_I32) {
4972        return false;
4973    }
4974
4975    if (src0->ne[0] <= 0) {
4976        return false;
4977    }
4978
4979    const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
4980    if (src0_nrows == 0) {
4981        return false;
4982    }
4983
4984    const int32_t * op_params = &op->op_params[0];
4985    const int n_dims = op_params[1];
4986    const int mode   = op_params[2];
4987    const int n_offs = op_params[15];
4988
4989    if (n_dims <= 0 || n_dims % 2 != 0) {
4990        return false;
4991    }
4992
4993    // ggml_rope_set_offset: HVX kernels need a VLEN-aligned window start (32 f32 elems)
4994    if (n_offs < 0 || (n_offs % 32 != 0) || (n_offs + n_dims > src0->ne[0])) {
4995        return false;
4996    }
4997
4998    float freq_base;
4999    memcpy(&freq_base, op_params + 5, sizeof(float));
5000    if (freq_base <= 0.0f) {
5001        return false;
5002    }
5003
5004    if (mode != GGML_ROPE_TYPE_NORMAL &&
5005        mode != GGML_ROPE_TYPE_NEOX &&
5006        mode != GGML_ROPE_TYPE_MROPE &&
5007        mode != GGML_ROPE_TYPE_VISION &&
5008        mode != GGML_ROPE_TYPE_IMROPE) {
5009        return false;
5010    }
5011
5012    const bool is_mrope = (mode & GGML_ROPE_TYPE_MROPE) != 0;
5013
5014    // n_dims == ne0/2, so the rotation spans the full row
5015    if (mode == GGML_ROPE_TYPE_VISION) {
5016        if (n_dims != (int) (src0->ne[0] / 2) || n_offs != 0) {
5017            return false;
5018        }
5019    }
5020
5021    if (is_mrope) {
5022        const int32_t * sections = op_params + 11;
5023        if (sections[0] <= 0 && sections[1] <= 0 && sections[2] <= 0) {
5024            return false;
5025        }
5026    }
5027
5028    const int64_t min_pos_len = (is_mrope || mode == GGML_ROPE_TYPE_VISION) ? src0->ne[2] * 4 : src0->ne[2];
5029    if (src1->ne[0] < min_pos_len || !ggml_is_contiguous(src1)) {
5030        return false;
5031    }
5032
5033    if (src2) {
5034        if (src2->type != GGML_TYPE_F32 || !ggml_is_contiguous(src2)) {
5035            return false;
5036        }
5037        if (src2->ne[0] < (n_dims / 2)) {
5038            return false;
5039        }
5040    }
5041
5042    // src0/dst elements within a row must be contiguous (nb[0] == sizeof(float)).
5043    // nb[1] may exceed ne[0]*sizeof(float) when the tensor is a strided view of a larger one
5044    if (src0->nb[0] != sizeof(float) || dst->nb[0] != sizeof(float)) {
5045        return false;
5046    }
5047    if (src0->nb[1] < src0->ne[0] * sizeof(float) || dst->nb[1] < dst->ne[0] * sizeof(float)) {
5048        return false;
5049    }
5050
5051    const uint32_t n_threads = (std::min)((uint32_t) sess->n_threads, src0_nrows);
5052
5053    struct htp_rope_vtcm_layout layout;
5054    htp_rope_vtcm_layout_build(&layout, src0->ne[0], n_threads);
5055    if (layout.total_bytes > sess->vtcm_size) {
5056        return false;
5057    }
5058
5059    return true;
5060}
5061
5062static bool ggml_hexagon_supported_ssm_conv(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5063    const struct ggml_tensor * src0 = op->src[0];
5064    const struct ggml_tensor * src1 = op->src[1];
5065    const struct ggml_tensor * dst  = op;
5066
5067    // Only support FP32 for now
5068    if (src0->type != GGML_TYPE_F32 || src1->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
5069        return false;
5070    }
5071
5072    // Check IO tensor shapes and dims
5073    if (src0->ne[3] != 1 || src1->ne[2] != 1 || src1->ne[3] != 1 || dst->ne[3] != 1) {
5074        return false; // src0 should be effectively 3D
5075    }
5076
5077    const int d_conv = src1->ne[0];
5078    const int d_inner = src0->ne[1];
5079    const int n_t = dst->ne[1];
5080    const int n_s = dst->ne[2];
5081
5082    if (src0->ne[0] != d_conv - 1 + n_t || src0->ne[1] != d_inner || src0->ne[2] != n_s) {
5083        return false;
5084    }
5085    if (src1->ne[0] != d_conv || src1->ne[1] != d_inner) {
5086        return false;
5087    }
5088    if (dst->ne[0] != d_inner || dst->ne[1] != n_t || dst->ne[2] != n_s) {
5089        return false;
5090    }
5091    if (src0->nb[0] != sizeof(float) || src1->nb[0] != sizeof(float) || dst->nb[0] != sizeof(float)) {
5092        return false;
5093    }
5094    if (src0->nb[1] != src0->ne[0] * sizeof(float) || src1->nb[1] != src1->ne[0] * sizeof(float)) {
5095        return false;
5096    }
5097
5098    return true;
5099
5100    GGML_UNUSED(sess);
5101}
5102
5103static bool ggml_hexagon_supported_im2col(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5104    const struct ggml_tensor * src1 = op->src[1];
5105    const struct ggml_tensor * dst  = op;
5106
5107    const bool is_2D = ((const int32_t *) op->op_params)[6] == 1;
5108    if (!is_2D) {
5109        return false;
5110    }
5111
5112    // For now support F32->F32 and F32->F16 only.
5113    if (src1->type != GGML_TYPE_F32 || (dst->type != GGML_TYPE_F16 && dst->type != GGML_TYPE_F32)) {
5114        return false;
5115    }
5116
5117    if (!ggml_is_contiguous(src1) || !ggml_is_contiguous(dst)) {
5118        return false;
5119    }
5120
5121    // For now keep padded OPs on CPU. Will revisit once we expand coverage past patch-embed OPs.
5122    const int32_t p0 = ((const int32_t *) op->op_params)[2];
5123    const int32_t p1 = ((const int32_t *) op->op_params)[3];
5124    if (p0 != 0 || p1 != 0) {
5125        return false;
5126    }
5127
5128    GGML_UNUSED(sess);
5129    return true;
5130}
5131
5132static bool ggml_hexagon_supported_pad(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5133    const struct ggml_tensor * src0 = op->src[0];
5134    const struct ggml_tensor * dst  = op;
5135
5136    if (src0->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
5137        return false;
5138    }
5139
5140    const int32_t lp0 = ((const int32_t *) op->op_params)[0];
5141    const int32_t rp0 = ((const int32_t *) op->op_params)[1];
5142    const int32_t circular = ((const int32_t *) op->op_params)[8];
5143
5144    if (circular && (lp0 > src0->ne[0] || rp0 > src0->ne[0])) {
5145        return false;
5146    }
5147
5148    return true;
5149
5150    GGML_UNUSED(sess);
5151}
5152
5153static bool ggml_hexagon_supported_cumsum(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5154    const struct ggml_tensor * src0 = op->src[0];
5155    const struct ggml_tensor * dst  = op;
5156
5157    if (src0->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
5158        return false;
5159    }
5160
5161    if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(dst)) {
5162        return false;
5163    }
5164
5165    return true;
5166
5167    GGML_UNUSED(sess);
5168}
5169
5170static bool ggml_hexagon_supported_diag(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5171    const struct ggml_tensor * src0 = op->src[0];
5172    const struct ggml_tensor * dst  = op;
5173
5174    // diag only supports F32 currently
5175    if (src0->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
5176        return false;
5177    }
5178
5179    // Input must have ne[1] == 1 (vector input)
5180    if (src0->ne[1] != 1) {
5181        return false;
5182    }
5183
5184    // Output must be square in first two dimensions
5185    if (dst->ne[0] != dst->ne[1] || dst->ne[0] != src0->ne[0]) {
5186        return false;
5187    }
5188
5189    return true;
5190
5191    GGML_UNUSED(sess);
5192}
5193
5194static bool ggml_hexagon_supported_solve_tri(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5195    const struct ggml_tensor * src0 = op->src[0]; // A
5196    const struct ggml_tensor * src1 = op->src[1]; // B
5197    const struct ggml_tensor * dst  = op;         // X
5198
5199    if (src0->type != GGML_TYPE_F32 || src1->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
5200        return false;
5201    }
5202
5203    if (src0->ne[0] != src0->ne[1]) {
5204        return false;
5205    }
5206
5207    if (src0->ne[1] != src1->ne[1]) {
5208        return false;
5209    }
5210
5211    if (src0->ne[2] != src1->ne[2] || src0->ne[3] != src1->ne[3]) {
5212        return false;
5213    }
5214
5215    if (dst->ne[0] != src1->ne[0] || dst->ne[1] != src1->ne[1] || dst->ne[2] != src1->ne[2] || dst->ne[3] != src1->ne[3]) {
5216        return false;
5217    }
5218
5219    return true;
5220
5221    GGML_UNUSED(sess);
5222}
5223
5224static bool ggml_hexagon_supported_tri(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
5225
5226    const struct ggml_tensor * src0 = op->src[0];
5227    const struct ggml_tensor * dst  = op;
5228
5229    if (src0->type != GGML_TYPE_F32) { return false; }
5230    if (dst->type  != GGML_TYPE_F32) { return false; }
5231    if (!ggml_are_same_shape(src0, dst)) { return false; }
5232    if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(dst)) { return false; }
5233
5234    return true;
5235
5236    GGML_UNUSED(sess);
5237}
5238
5239static const char * ggml_backend_hexagon_name(ggml_backend_t backend) {
5240    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5241    return sess->c_name();
5242}
5243
5244static void ggml_backend_hexagon_free(ggml_backend_t backend) {
5245    // we just need to delete the backend here
5246    // the sessions are allocated & freed as part of the registry
5247    delete backend;
5248}
5249
5250static htp_op_code op_remap_to_htp(const ggml_tensor * t) {
5251    switch (t->op) {
5252        case GGML_OP_FLASH_ATTN_EXT:  return HTP_OP_FLASH_ATTN_EXT;
5253        case GGML_OP_MUL_MAT:         return HTP_OP_MUL_MAT;
5254        case GGML_OP_MUL_MAT_ID:      return HTP_OP_MUL_MAT_ID;
5255        case GGML_OP_MUL:             return HTP_OP_MUL;
5256        case GGML_OP_ADD:             return HTP_OP_ADD;
5257        case GGML_OP_ADD_ID:          return HTP_OP_ADD_ID;
5258        case GGML_OP_SUB:             return HTP_OP_SUB;
5259        case GGML_OP_DIV:             return HTP_OP_DIV;
5260        case GGML_OP_CPY:             return HTP_OP_CPY;
5261        case GGML_OP_CONT:            return HTP_OP_CPY;
5262        case GGML_OP_GET_ROWS:        return HTP_OP_GET_ROWS;
5263        case GGML_OP_SET_ROWS:        return HTP_OP_SET_ROWS;
5264        case GGML_OP_SUM_ROWS:        return HTP_OP_SUM_ROWS;
5265        case GGML_OP_ARGSORT:         return HTP_OP_ARGSORT;
5266        case GGML_OP_NORM:            return HTP_OP_NORM;
5267        case GGML_OP_L2_NORM:         return HTP_OP_L2_NORM;
5268        case GGML_OP_RMS_NORM:        return HTP_OP_RMS_NORM;
5269        case GGML_OP_CONCAT:          return HTP_OP_CONCAT;
5270        case GGML_OP_SCALE:           return HTP_OP_SCALE;
5271        case GGML_OP_CLAMP:           return HTP_OP_CLAMP;
5272        case GGML_OP_LEAKY_RELU:      return HTP_OP_LEAKY_RELU;
5273        case GGML_OP_SQR:             return HTP_OP_SQR;
5274        case GGML_OP_SQRT:            return HTP_OP_SQRT;
5275        case GGML_OP_LOG:             return HTP_OP_UNARY_LOG;
5276        case GGML_OP_SOFT_MAX:        return HTP_OP_SOFTMAX;
5277        case GGML_OP_SSM_CONV:        return HTP_OP_SSM_CONV;
5278        case GGML_OP_GATED_DELTA_NET: return HTP_OP_GATED_DELTA_NET;
5279        case GGML_OP_ROPE:            return HTP_OP_ROPE;
5280        case GGML_OP_REPEAT:          return HTP_OP_REPEAT;
5281        case GGML_OP_CUMSUM:          return HTP_OP_CUMSUM;
5282        case GGML_OP_FILL:            return HTP_OP_FILL;
5283        case GGML_OP_DIAG:            return HTP_OP_DIAG;
5284        case GGML_OP_SOLVE_TRI:       return HTP_OP_SOLVE_TRI;
5285        case GGML_OP_TRI:             return HTP_OP_TRI;
5286        case GGML_OP_PAD:             return HTP_OP_PAD;
5287        case GGML_OP_IM2COL:          return HTP_OP_IM2COL;
5288
5289        case GGML_OP_UNARY:
5290            switch (ggml_get_unary_op(t)) {
5291                case GGML_UNARY_OP_SILU:       return HTP_OP_UNARY_SILU;
5292                case GGML_UNARY_OP_GELU:       return HTP_OP_UNARY_GELU;
5293                case GGML_UNARY_OP_GELU_QUICK: return HTP_OP_UNARY_GELU;
5294                case GGML_UNARY_OP_SIGMOID:    return HTP_OP_UNARY_SIGMOID;
5295                case GGML_UNARY_OP_NEG:        return HTP_OP_UNARY_NEG;
5296                case GGML_UNARY_OP_EXP:        return HTP_OP_UNARY_EXP;
5297                case GGML_UNARY_OP_SOFTPLUS:   return HTP_OP_UNARY_SOFTPLUS;
5298                case GGML_UNARY_OP_TANH:       return HTP_OP_UNARY_TANH;
5299                case GGML_UNARY_OP_ABS:        return HTP_OP_UNARY_ABS;
5300                case GGML_UNARY_OP_RELU:       return HTP_OP_UNARY_RELU;
5301            default:
5302                break;
5303            }
5304            break;
5305
5306        case GGML_OP_GLU:
5307            switch (ggml_get_glu_op(t)) {
5308                case GGML_GLU_OP_SWIGLU:     return HTP_OP_GLU_SWIGLU;
5309                case GGML_GLU_OP_SWIGLU_OAI: return HTP_OP_GLU_SWIGLU_OAI;
5310                case GGML_GLU_OP_SWIGLU_CLAMP: return HTP_OP_GLU_SWIGLU_CLAMP;
5311                case GGML_GLU_OP_GEGLU:      return HTP_OP_GLU_GEGLU;
5312                default: break;
5313            }
5314            break;
5315
5316        default:
5317            GGML_ABORT("\nggml-hex: graph-compute %s is not supported\n", ggml_op_desc(t));
5318    }
5319    return HTP_OP_INVALID;
5320}
5321
5322static inline bool op_is_compute(ggml_tensor *node)
5323{
5324    return !ggml_op_is_empty(node->op) && !ggml_is_empty(node) && (node->flags & GGML_TENSOR_FLAG_COMPUTE);
5325}
5326
5327static bool mm_is_hmx_eligible(const ggml_tensor * t) {
5328    if (opt_nhmx == 0) { return false; }
5329
5330    const ggml_tensor * src0 = t->src[0];
5331    const ggml_tensor * src1 = t->src[1];
5332
5333    const int wtype = src0->type;
5334    const bool is_repack    = ggml_hexagon_is_repack_type((ggml_type) wtype);
5335    const bool is_matmul_id = (t->op == GGML_OP_MUL_MAT_ID);
5336    const bool is_batched   = (src0->ne[2] * src0->ne[3] > 1 || src1->ne[2] * src1->ne[3] > 1);
5337
5338    const int ne01_padded = is_repack ? hex_round_up(src0->ne[1], 32) : src0->ne[1];
5339
5340    return ggml_hexagon_matmul_is_hmx_eligible(src0, src1, t, ne01_padded, is_matmul_id, is_batched);
5341}
5342
5343static bool is_supported_mul_mat_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams) {
5344    if (kparams->n_hmx) {
5345        return kparams->kernel_type == HTP_MM_KERNEL_HMX_2D;
5346    }
5347
5348    if (!ggml_hexagon_is_repack_type(src0->type)) {
5349        return false;
5350    }
5351
5352    return kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW || kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW_FLAT;
5353}
5354
5355static bool is_supported_mul_mat_id_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams) {
5356    if (kparams->n_hmx) {
5357        return kparams->kernel_type == HTP_MM_KERNEL_HMX_2D;
5358    }
5359
5360    if (!ggml_hexagon_is_repack_type(src0->type)) {
5361        return false;
5362    }
5363
5364    return kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW || kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_BLOCK;
5365}
5366
5367static bool is_mergeable_mul_mat(const ggml_tensor * t) {
5368    if (t->op != GGML_OP_MUL_MAT) return false;
5369
5370    const ggml_tensor * src0 = t->src[0];
5371    const ggml_tensor * src1 = t->src[1];
5372    if (src1->type != GGML_TYPE_F32) return false;
5373    if (src0->ne[2] != 1 || src0->ne[3] != 1) return false;
5374
5375    if (mm_is_hmx_eligible(t)) {
5376        return ggml_hexagon_is_hmx_weight_type(src0->type);
5377    }
5378
5379    return ggml_hexagon_is_repack_type(src0->type);
5380}
5381
5382static bool is_mergeable_mul_mat_pair(const ggml_tensor * n1, const ggml_tensor * n2) {
5383    if (!is_mergeable_mul_mat(n1) || !is_mergeable_mul_mat(n2)) {
5384        return false;
5385    }
5386    if (n1->src[1] != n2->src[1]) {
5387        return false;
5388    }
5389    if (n1->src[0]->ne[0] != n2->src[0]->ne[0]) {
5390        return false;
5391    }
5392    if (n1->src[0]->type != n2->src[0]->type) {
5393        return false;
5394    }
5395    if (mm_is_hmx_eligible(n1) != mm_is_hmx_eligible(n2)) {
5396        return false;
5397    }
5398    return true;
5399}
5400
5401static bool is_mergeable_mul_mat_id(const ggml_tensor * t) {
5402    if (t->op != GGML_OP_MUL_MAT_ID) return false;
5403
5404    const ggml_tensor * src0 = t->src[0];
5405    return ggml_hexagon_is_repack_type(src0->type);
5406}
5407
5408static bool is_mergeable_mul_mat_id_pair(const ggml_tensor * n1, const ggml_tensor * n2) {
5409    if (!is_mergeable_mul_mat_id(n1) || !is_mergeable_mul_mat_id(n2)) {
5410        return false;
5411    }
5412    if (n1->src[1] != n2->src[1]) {
5413        return false;
5414    }
5415    if (n1->src[2] != n2->src[2]) {
5416        return false;
5417    }
5418    if (n1->src[0]->ne[0] != n2->src[0]->ne[0]) {
5419        return false;
5420    }
5421    if (n1->src[0]->ne[2] != n2->src[0]->ne[2]) {
5422        return false;
5423    }
5424    if (n1->src[0]->type != n2->src[0]->type) {
5425        return false;
5426    }
5427    if (mm_is_hmx_eligible(n1) != mm_is_hmx_eligible(n2)) {
5428        return false;
5429    }
5430    return true;
5431}
5432
5433static ggml_status ggml_backend_hexagon_graph_compute(ggml_backend_t backend, ggml_cgraph * graph) {
5434    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5435
5436    if (sess->last_error > HTP_STATUS_OK) {
5437        return GGML_STATUS_FAILED;
5438    }
5439
5440    HEX_VERBOSE("ggml-hex: %s graph-compute n_nodes %d\n", sess->c_name(), graph->n_nodes);
5441
5442    const std::vector<htp_opnode> * nodes_ptr = nullptr;
5443    std::vector<htp_opnode> computed_nodes;
5444
5445    // Check for cache hit
5446    bool cache_hit = (graph->uid != 0 && sess->cached_uid == graph->uid);
5447    if (cache_hit) {
5448        nodes_ptr = &sess->cached_nodes;
5449    } else {
5450        // Tag fusable tensors in graph
5451        for (int i = 0; i < graph->n_nodes; i++) {
5452            auto * extra = (ggml_hexagon_tensor_extra *) graph->nodes[i]->extra;
5453            if (!extra) continue;
5454
5455            extra->flags &= ~GGML_HEXAGON_TENSOR_FUSEABLE;
5456
5457            if (graph->nodes[i]->op == GGML_OP_RMS_NORM && ggml_can_fuse(graph, i, { GGML_OP_RMS_NORM, GGML_OP_MUL })) {
5458                extra->flags |= GGML_HEXAGON_TENSOR_FUSEABLE;
5459            } else if (graph->nodes[i]->op == GGML_OP_MUL_MAT || graph->nodes[i]->op == GGML_OP_MUL_MAT_ID) {
5460                if ((i + 1 < graph->n_nodes && graph->nodes[i + 1]->op == GGML_OP_ADD && ggml_can_fuse(graph, i, { graph->nodes[i]->op, GGML_OP_ADD })) ||
5461                    ggml_node_has_n_uses(graph, i, 1)) {
5462                    extra->flags |= GGML_HEXAGON_TENSOR_FUSEABLE;
5463                }
5464            }
5465        }
5466
5467        computed_nodes.reserve(graph->n_nodes);
5468
5469        for (int i = 0; i < graph->n_nodes; ++i) {
5470            ggml_tensor * n = graph->nodes[i];
5471            if (!op_is_compute(n)) {
5472                continue;
5473            }
5474
5475            htp_opnode node(HTP_OP_INVALID, n);
5476            node.opcode = op_remap_to_htp(n);
5477            if (node.opcode == HTP_OP_MUL_MAT || node.opcode == HTP_OP_MUL_MAT_ID) {
5478                ggml_hexagon_precompute_matmul_params(sess,
5479                    node.node->src[0], node.node->src[1], node.node,
5480                    (struct htp_mm_kernel_params *)node.kernel_params
5481                );
5482            } else if (node.opcode == HTP_OP_FLASH_ATTN_EXT) {
5483                ggml_hexagon_precompute_flash_attn_params(sess,
5484                    node.node,
5485                    (struct htp_fa_kernel_params *)node.kernel_params
5486                );
5487            } else if (htp_op_is_unary(node.opcode)) {
5488                auto inputs = node.get_inputs();
5489                const struct ggml_tensor * src0 = inputs[0];
5490                const struct ggml_tensor * src1 = inputs.size() > 1 ? inputs[1] : nullptr;
5491                ggml_hexagon_precompute_unary_params(sess,
5492                    node.opcode, src0, src1, node.dst(),
5493                    (struct htp_unary_kernel_params *)node.kernel_params
5494                );
5495            } else if (node.opcode == HTP_OP_GET_ROWS) {
5496                ggml_hexagon_precompute_get_rows_params(sess,
5497                    node.node->src[0], node.node->src[1], node.dst(),
5498                    (struct htp_get_rows_kernel_params *)node.kernel_params
5499                );
5500            } else if (node.opcode == HTP_OP_SET_ROWS) {
5501                ggml_hexagon_precompute_set_rows_params(sess,
5502                    node.node->src[0], node.node->src[1], node.dst(),
5503                    (struct htp_set_rows_kernel_params *)node.kernel_params
5504                );
5505            } else if (node.opcode == HTP_OP_ROPE) {
5506                ggml_hexagon_precompute_rope_params(sess,
5507                    node.node,
5508                    (struct htp_rope_kernel_params *)node.kernel_params
5509                );
5510            }
5511            computed_nodes.push_back(std::move(node));
5512        }
5513
5514        if (graph->uid != 0) {
5515            sess->cached_uid   = graph->uid;
5516            sess->cached_nodes = std::move(computed_nodes);
5517            nodes_ptr = &sess->cached_nodes;
5518        } else {
5519            nodes_ptr = &computed_nodes;
5520        }
5521    }
5522
5523    // Queue and execute
5524    for (const auto & node : *nodes_ptr) {
5525        sess->enqueue_op(node);
5526    }
5527
5528    if (sess->last_error > HTP_STATUS_OK) {
5529        return GGML_STATUS_FAILED;
5530    }
5531
5532    return GGML_STATUS_SUCCESS;
5533}
5534
5535static void ggml_backend_hexagon_synchronize(ggml_backend_t backend) {
5536    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5537
5538    HEX_VERBOSE("ggml-hex: %s synchronize\n", sess->c_name());
5539
5540    // Wait until all pending ops complete
5541    sess->flush_sync();
5542    if (sess->last_error > HTP_STATUS_OK) {
5543        GGML_ABORT("ggml-hex: %s synchronize failed : dsp-error %s\n", sess->c_name(), status_to_str(sess->last_error));
5544    }
5545}
5546
5547enum ggml_hexagon_mem_range_type {
5548    HEXAGON_MEM_RANGE_TYPE_SRC,
5549    HEXAGON_MEM_RANGE_TYPE_DST,
5550};
5551
5552struct ggml_hexagon_mem_range {
5553    uint64_t pb;
5554    uint64_t p0;
5555    uint64_t p1;
5556    ggml_hexagon_mem_range_type pt;
5557};
5558
5559struct ggml_hexagon_mem_ranges {
5560    std::vector<ggml_hexagon_mem_range> ranges;
5561
5562    void reset() {
5563        ranges.clear();
5564    }
5565
5566    void add(const ggml_hexagon_mem_range & mr) {
5567        ranges.push_back(mr);
5568    }
5569
5570    bool check(const ggml_hexagon_mem_range & mr) const {
5571        for (const auto & cmp : ranges) {
5572            if (mr.pb != cmp.pb) {
5573                continue;
5574            }
5575            if (mr.pt == HEXAGON_MEM_RANGE_TYPE_SRC && cmp.pt == HEXAGON_MEM_RANGE_TYPE_SRC) {
5576                continue;
5577            }
5578            if (mr.p0 < cmp.p1 && mr.p1 > cmp.p0) {
5579                return false;
5580            }
5581        }
5582        return true;
5583    }
5584};
5585
5586static ggml_hexagon_mem_range ggml_hexagon_mem_range_from_tensor(const ggml_tensor * tensor, ggml_hexagon_mem_range_type pt) {
5587    const ggml_tensor * base = tensor->view_src ? tensor->view_src : tensor;
5588    ggml_hexagon_mem_range mr;
5589    if (tensor->buffer) {
5590        mr = {
5591            /*.pb =*/ (uint64_t) tensor->buffer,
5592            /*.p0 =*/ (uint64_t) tensor->data,
5593            /*.p1 =*/ (uint64_t) tensor->data + ggml_backend_buft_get_alloc_size(tensor->buffer->buft, tensor),
5594            /*.pt =*/ pt,
5595        };
5596    } else {
5597        mr = {
5598            /*.pb =*/ (uint64_t) base,
5599            /*.p0 =*/ 0,
5600            /*.p1 =*/ 1024,
5601            /*.pt =*/ pt,
5602        };
5603    }
5604    return mr;
5605}
5606
5607static void ggml_hexagon_mem_ranges_add_node(ggml_hexagon_mem_ranges & mrs, const htp_opnode & node) {
5608    if (node.is_empty()) return;
5609
5610    for (int i = 0; i < GGML_MAX_SRC; i++) {
5611        if (node.node->src[i]) {
5612            mrs.add(ggml_hexagon_mem_range_from_tensor(node.node->src[i], HEXAGON_MEM_RANGE_TYPE_SRC));
5613        }
5614    }
5615    for (const auto * fused : node.fused) {
5616        for (int i = 0; i < GGML_MAX_SRC; i++) {
5617            if (fused->src[i]) {
5618                mrs.add(ggml_hexagon_mem_range_from_tensor(fused->src[i], HEXAGON_MEM_RANGE_TYPE_SRC));
5619            }
5620        }
5621    }
5622    mrs.add(ggml_hexagon_mem_range_from_tensor(node.dst(), HEXAGON_MEM_RANGE_TYPE_DST));
5623}
5624
5625static bool ggml_hexagon_mem_ranges_check_node(const ggml_hexagon_mem_ranges & mrs, const htp_opnode & node) {
5626    if (node.is_empty()) return true;
5627
5628    for (int i = 0; i < GGML_MAX_SRC; i++) {
5629        if (node.node->src[i]) {
5630            if (!mrs.check(ggml_hexagon_mem_range_from_tensor(node.node->src[i], HEXAGON_MEM_RANGE_TYPE_SRC))) {
5631                return false;
5632            }
5633        }
5634    }
5635    for (const auto * fused : node.fused) {
5636        for (int i = 0; i < GGML_MAX_SRC; i++) {
5637            if (fused->src[i]) {
5638                if (!mrs.check(ggml_hexagon_mem_range_from_tensor(fused->src[i], HEXAGON_MEM_RANGE_TYPE_SRC))) {
5639                    return false;
5640                }
5641            }
5642        }
5643    }
5644    return mrs.check(ggml_hexagon_mem_range_from_tensor(node.dst(), HEXAGON_MEM_RANGE_TYPE_DST));
5645}
5646
5647static std::vector<int> ggml_hexagon_graph_optimize_reorder(const std::vector<htp_opnode> & nodes) {
5648    const int n = nodes.size();
5649
5650    std::vector<int> res;
5651    res.reserve(n);
5652
5653    std::vector<bool> used(n, false);
5654
5655    ggml_hexagon_mem_ranges mrs;
5656
5657    // The main goal here is to stack the MUL_MAT ops with the same src1 input.
5658    // This allows us to reuse dynamically quantized src1 in VTCM.
5659
5660    for (int i0 = 0; i0 < n; i0++) {
5661        if (used[i0]) {
5662            continue;
5663        }
5664
5665        const auto & node0 = nodes[i0];
5666
5667        if (!node0.stackable()) {
5668            res.push_back(i0);
5669            used[i0] = true;
5670            continue;
5671        }
5672
5673        // that many nodes forward to search for stackable nodes that can reuse VTCM
5674        constexpr int N_FORWARD = 16;
5675
5676        std::vector<int> stack;
5677        stack.push_back(i0);
5678
5679        mrs.reset();
5680
5681        for (int i1 = i0 + 1; i1 < i0 + N_FORWARD && i1 < n; i1++) {
5682            if (used[i1]) {
5683                continue;
5684            }
5685
5686            const auto & node1 = nodes[i1];
5687
5688            if (node1.stackable() && node1.same_input(node0) && ggml_hexagon_mem_ranges_check_node(mrs, node1)) {
5689                stack.push_back(i1);
5690            } else {
5691                ggml_hexagon_mem_ranges_add_node(mrs, node1);
5692            }
5693        }
5694
5695        for (int idx : stack) {
5696            res.push_back(idx);
5697            used[idx] = true;
5698        }
5699    }
5700
5701    return res;
5702}
5703
5704static void ggml_backend_hexagon_graph_optimize(ggml_backend_t backend, ggml_cgraph * gf, ggml_backend_graph_optimize_params * params) {
5705    GGML_UNUSED(params);
5706
5707    const int n = gf->n_nodes;
5708
5709    constexpr int MAX_FUSE = 16;
5710
5711    enum ggml_op ops[MAX_FUSE];
5712
5713    std::vector<htp_opnode> nodes;
5714    nodes.reserve(gf->n_nodes);
5715
5716    // Pack nodes for reordering
5717    for (int i = 0; i < n; i++) {
5718        htp_opnode node(HTP_OP_INVALID, gf->nodes[i]);
5719
5720        // fuse only ops that start with these operations
5721        // can be expanded when needed
5722        if (node.op() == GGML_OP_ADD ||
5723            node.op() == GGML_OP_NORM ||
5724            node.op() == GGML_OP_RMS_NORM) {
5725            ops[0] = node.op();
5726
5727            int f = i + 1;
5728            while (f < n && f < i + MAX_FUSE) {
5729                // conservatively allow fusing only these ops
5730                // can be expanded when needed
5731                if (gf->nodes[f]->op != GGML_OP_ADD &&
5732                    gf->nodes[f]->op != GGML_OP_MUL &&
5733                    gf->nodes[f]->op != GGML_OP_NORM &&
5734                    gf->nodes[f]->op != GGML_OP_RMS_NORM) {
5735                    break;
5736                }
5737                ops[f - i] = gf->nodes[f]->op;
5738                f++;
5739            }
5740
5741            f -= i;
5742            for (; f > 1; f--) {
5743                if (ggml_can_fuse(gf, i, ops, f)) {
5744                    break;
5745                }
5746            }
5747
5748            // add the fused tensors into the node info so we can unfuse them later
5749            for (int k = 1; k < f; k++) {
5750                ++i;
5751
5752                // the .dst() becomes the last fused tensor
5753                node.add_fused(gf->nodes[i]);
5754            }
5755        }
5756
5757        nodes.push_back(std::move(node));
5758    }
5759
5760    const auto order = ggml_hexagon_graph_optimize_reorder(nodes);
5761
5762    // unfuse
5763    {
5764        int j = 0;
5765        for (const auto i : order) {
5766            const auto & node = nodes[i];
5767
5768            gf->nodes[j++] = node.node;
5769
5770            for (auto * fused : node.fused) {
5771                gf->nodes[j++] = fused;
5772            }
5773        }
5774    }
5775
5776    GGML_UNUSED(backend);
5777}
5778
5779static uint64_t ggml_hexagon_session_key(const ggml_hexagon_session * sess) {
5780    return ((uint64_t) (uint32_t) sess->phys_idx << 32) | (uint32_t) sess->virt_idx;
5781}
5782
5783static bool ggml_hexagon_cpy_tensor_async_phys(ggml_backend_t backend_src, ggml_backend_t backend_dst, const ggml_tensor * src, ggml_tensor * dst) {
5784    auto sess_src = static_cast<ggml_hexagon_session *>(backend_src->context);
5785    auto sess_dst = static_cast<ggml_hexagon_session *>(backend_dst->context);
5786    auto sbuf_dst = (ggml_hexagon_shared_buffer *) dst->buffer->context;
5787
5788    if (!sess_src->clone_buffer(sbuf_dst)) { return false; }
5789
5790    const uint64_t src_key = ggml_hexagon_session_key(sess_src);
5791    auto & fence_slot = sess_dst->cpy_fence_slots[src_key];
5792    if (!fence_slot) {
5793        fence_slot = (volatile uint32_t *) sess_dst->alloc_fence(1);
5794    }
5795
5796    if (!sess_src->clone_buffer(sess_dst->fence_buf)) { return false; }
5797
5798    if (++sess_dst->fence_seq == 0) sess_dst->fence_seq = 1;
5799    uint32_t fence_seq = sess_dst->fence_seq;
5800
5801    HEX_VERBOSE("ggml-hex: %s cpy-tensor-async %s -> %s size %zu : seq 0x%x\n",
5802                sess_dst->name.c_str(), src->name, dst->name, ggml_nbytes(src), fence_seq);
5803
5804    // dummy fence extra (must be static)
5805    static ggml_hexagon_tensor_extra fence_extra { {}, 0, GGML_HEXAGON_TENSOR_FENCE };
5806
5807    ggml_tensor fence_tensor {};
5808    fence_tensor.buffer = &sess_dst->fence_buf->backend_buffer;
5809    fence_tensor.extra  = &fence_extra;
5810    fence_tensor.data   = (void *) fence_slot;
5811    fence_tensor.type   = GGML_TYPE_I32;
5812    fence_tensor.ne[0]  = 1;
5813    fence_tensor.ne[1]  = 1;
5814    fence_tensor.ne[2]  = 1;
5815    fence_tensor.ne[3]  = 1;
5816    fence_tensor.nb[0]  = sizeof(int32_t);
5817    fence_tensor.nb[1]  = sizeof(int32_t);
5818    fence_tensor.nb[2]  = sizeof(int32_t);
5819    fence_tensor.nb[3]  = sizeof(int32_t);
5820    fence_tensor.op     = GGML_OP_NONE;
5821
5822    sess_src->enqueue_cpy(src, dst, &fence_tensor, fence_seq);
5823    sess_dst->enqueue_fence(&fence_tensor, fence_seq, /* wait = */ true);
5824
5825    sess_dst->add_peer(sess_src);
5826
5827    return true;
5828}
5829
5830static bool ggml_hexagon_cpy_tensor_async_virt(ggml_backend_t backend_src, ggml_backend_t backend_dst, const ggml_tensor * src, ggml_tensor * dst) {
5831    auto sess_src = static_cast<ggml_hexagon_session *>(backend_src->context);
5832    auto sess_dst = static_cast<ggml_hexagon_session *>(backend_dst->context);
5833    auto sbuf_src = (ggml_hexagon_shared_buffer *) src->buffer->context;
5834
5835    if (!sess_dst->clone_buffer(sbuf_src)) { return false; }
5836
5837    HEX_VERBOSE("ggml-hex: %s cpy-tensor-async %s -> %s size %zu\n",
5838                sess_dst->name.c_str(), src->name, dst->name, ggml_nbytes(src));
5839
5840    sess_dst->enqueue_cpy(src, dst);
5841    sess_dst->add_peer(sess_src);
5842
5843    return true;
5844}
5845
5846static bool ggml_backend_hexagon_cpy_tensor_async(ggml_backend_t backend_src, ggml_backend_t backend_dst, const ggml_tensor * src, ggml_tensor * dst) {
5847    if (!ggml_backend_is_hexagon(backend_src) || !ggml_backend_is_hexagon(backend_dst)) {
5848        return false;
5849    }
5850
5851    // FIXME: ggml-meta needs to call init_tensor on auxiliary tensors
5852    if (!dst->extra) {
5853        ggml_backend_buffer_init_tensor(dst->buffer, dst);
5854    }
5855
5856    auto * dst_extra = static_cast<ggml_hexagon_tensor_extra *>(dst->extra);
5857    const auto * src_extra = static_cast<const ggml_hexagon_tensor_extra *>(src->extra);
5858    dst_extra->flags = src_extra->flags & ~GGML_HEXAGON_TENSOR_FUSEABLE;
5859
5860    auto sess_src = static_cast<ggml_hexagon_session *>(backend_src->context);
5861    auto sess_dst = static_cast<ggml_hexagon_session *>(backend_dst->context);
5862
5863    if (sess_src == sess_dst) {
5864        HEX_VERBOSE("ggml-hex: %s cpy-tensor-async %s -> %s size %zu\n", sess_dst->name.c_str(), src->name, dst->name, ggml_nbytes(src));
5865        sess_src->enqueue_cpy(src, dst);
5866        return true;
5867    }
5868
5869    if (sess_src->phys_idx != sess_dst->phys_idx)
5870        return ggml_hexagon_cpy_tensor_async_phys(backend_src, backend_dst, src, dst);
5871
5872    return ggml_hexagon_cpy_tensor_async_virt(backend_src, backend_dst, src, dst);
5873}
5874
5875static ggml_backend_event_t ggml_backend_hexagon_device_event_new(ggml_backend_dev_t dev) {
5876    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
5877    auto sess    = dev_ctx->session();
5878
5879    ggml_hexagon_event * hex_event = new ggml_hexagon_event();
5880    hex_event->fence_sess = sess;
5881    hex_event->sess       = sess;
5882    hex_event->fence_slot = (volatile uint32_t *) sess->alloc_fence(1);
5883
5884    static ggml_hexagon_tensor_extra fence_extra { {}, 0, GGML_HEXAGON_TENSOR_FENCE };
5885    hex_event->fence_tensor.buffer = &sess->fence_buf->backend_buffer;
5886    hex_event->fence_tensor.extra  = &fence_extra;
5887    hex_event->fence_tensor.data   = (void *) hex_event->fence_slot;
5888    hex_event->fence_tensor.type   = GGML_TYPE_I32;
5889    hex_event->fence_tensor.ne[0]  = 1;
5890    hex_event->fence_tensor.ne[1]  = 1;
5891    hex_event->fence_tensor.ne[2]  = 1;
5892    hex_event->fence_tensor.ne[3]  = 1;
5893    hex_event->fence_tensor.nb[0]  = sizeof(int32_t);
5894    hex_event->fence_tensor.nb[1]  = sizeof(int32_t);
5895    hex_event->fence_tensor.nb[2]  = sizeof(int32_t);
5896    hex_event->fence_tensor.nb[3]  = sizeof(int32_t);
5897    hex_event->fence_tensor.op     = GGML_OP_NONE;
5898
5899    HEX_VERBOSE("ggml-hex: %s event-new : event %p fence %p\n", ggml_backend_dev_name(dev), (void *)hex_event, (void *)hex_event->fence_slot);
5900
5901    return new ggml_backend_event {
5902        /* .device  = */ dev,
5903        /* .context = */ hex_event,
5904    };
5905}
5906
5907static void ggml_hexagon_event_synchronize(ggml_backend_dev_t dev, ggml_hexagon_event * hex_event) {
5908    if (hex_event->seq == 0) {
5909        return;
5910    }
5911
5912    HEX_VERBOSE("ggml-hex: %s event-synchronize : event %p seq 0x%x fence %p\n",
5913                ggml_backend_dev_name(dev), (void *)hex_event, hex_event->seq, (void *)hex_event->fence_slot);
5914
5915    auto * fence = reinterpret_cast<const volatile std::atomic<uint32_t> *>(hex_event->fence_slot);
5916
5917    if ((int32_t)(fence[0].load(std::memory_order_relaxed) - hex_event->seq) < 0) {
5918        hex_event->sess->flush_async();
5919    }
5920
5921    while (true) {
5922        if ((int32_t)(fence[0].load(std::memory_order_acquire) - hex_event->seq) >= 0) {
5923            uint32_t status = fence[1].load(std::memory_order_acquire);
5924            if (status > HTP_STATUS_OK) {
5925                GGML_ABORT("ggml-hex: %s event-synchronize failed : dsp-error %s\n",
5926                           hex_event->sess->c_name(), status_to_str(status));
5927            }
5928            break;
5929        }
5930        std::this_thread::yield();
5931    }
5932}
5933
5934static void ggml_backend_hexagon_device_event_free(ggml_backend_dev_t dev, ggml_backend_event_t event) {
5935    auto * hex_event = static_cast<ggml_hexagon_event *>(event->context);
5936    ggml_hexagon_event_synchronize(dev, hex_event);
5937    HEX_VERBOSE("ggml-hex: %s event-free : event %p\n", ggml_backend_dev_name(dev), (void *)hex_event);
5938    hex_event->fence_sess->free_fence((void *) hex_event->fence_slot, 1);
5939    delete hex_event;
5940    delete event;
5941}
5942
5943static void ggml_backend_hexagon_device_event_synchronize(ggml_backend_dev_t dev, ggml_backend_event_t event) {
5944    auto * hex_event = static_cast<ggml_hexagon_event *>(event->context);
5945    ggml_hexagon_event_synchronize(dev, hex_event);
5946}
5947
5948static void ggml_backend_hexagon_event_record(ggml_backend_t backend, ggml_backend_event_t event) {
5949    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5950    auto hex_event = static_cast<ggml_hexagon_event *>(event->context);
5951
5952    if (++sess->fence_seq == 0) sess->fence_seq = 1;
5953    hex_event->sess = sess;
5954    hex_event->seq  = sess->fence_seq;
5955
5956    sess->enqueue_fence(&hex_event->fence_tensor, hex_event->seq, /* wait = */ false);
5957
5958    HEX_VERBOSE("ggml-hex: %s event-record : event %p seq 0x%x fence %p\n",
5959                sess->c_name(), (void *)hex_event, hex_event->seq, (void *)hex_event->fence_slot);
5960}
5961
5962static void ggml_backend_hexagon_event_wait(ggml_backend_t backend, ggml_backend_event_t event) {
5963    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5964    auto hex_event = static_cast<ggml_hexagon_event *>(event->context);
5965
5966    if (hex_event->seq == 0) {
5967        return;
5968    }
5969
5970    HEX_VERBOSE("ggml-hex: %s event-wait : event %p seq 0x%x fence %p\n",
5971                sess->c_name(), (void *)hex_event, hex_event->seq, (void *)hex_event->fence_slot);
5972
5973    // same physical NPU runs sequentially in FIFO order
5974    if (sess->phys_idx == hex_event->sess->phys_idx) {
5975        if (sess != hex_event->sess) {
5976            sess->add_peer(hex_event->sess);
5977        }
5978        return;
5979    }
5980
5981    sess->clone_buffer(hex_event->fence_sess->fence_buf);
5982    sess->add_peer(hex_event->sess);
5983    sess->enqueue_fence(&hex_event->fence_tensor, hex_event->seq, /* wait = */ true);
5984}
5985
5986static void ggml_backend_hexagon_set_tensor_async(ggml_backend_t backend, struct ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
5987    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5988    HEX_VERBOSE("ggml-hex: %s set-tensor-async %s : data %p offset %zu size %zu usage %d\n",
5989                sess->c_name(), tensor->name, data, offset, size, tensor->buffer ? (int) tensor->buffer->usage : -1);
5990    ggml_backend_tensor_set(tensor, data, offset, size);
5991}
5992
5993static void ggml_backend_hexagon_get_tensor_async(ggml_backend_t backend, const struct ggml_tensor * tensor, void * data, size_t offset, size_t size) {
5994    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
5995    HEX_VERBOSE("ggml-hex: %s get-tensor-async %s : data %p offset %zu size %zu usage %d\n",
5996                sess->c_name(), tensor->name, data, offset, size, tensor->buffer ? (int) tensor->buffer->usage : -1);
5997    sess->flush_sync();
5998    if (sess->last_error > HTP_STATUS_OK) {
5999        GGML_ABORT("ggml-hex: %s get-tensor-async failed : dsp-error %s\n", sess->c_name(), status_to_str(sess->last_error));
6000    }
6001    ggml_backend_tensor_get(tensor, data, offset, size);
6002}
6003
6004static void ggml_backend_hexagon_set_tensor_2d_async(ggml_backend_t backend,
6005                                                     struct ggml_tensor * tensor,
6006                                                     const void * data,
6007                                                     size_t offset,
6008                                                     size_t size,
6009                                                     size_t n_copies,
6010                                                     size_t stride_tensor,
6011                                                     size_t stride_data) {
6012    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
6013    HEX_VERBOSE("ggml-hex: %s set-tensor-2d-async %s : data %p offset %zu size %zu n_copies %zu stride_tensor %zu stride_data %zu usage %d\n",
6014                sess->c_name(), tensor->name, data, offset, size, n_copies, stride_tensor, stride_data, tensor->buffer ? (int) tensor->buffer->usage : -1);
6015    ggml_backend_tensor_set_2d(tensor, data, offset, size, n_copies, stride_tensor, stride_data);
6016}
6017
6018static void ggml_backend_hexagon_get_tensor_2d_async(ggml_backend_t backend,
6019                                                     const struct ggml_tensor * tensor,
6020                                                     void * data,
6021                                                     size_t offset,
6022                                                     size_t size,
6023                                                     size_t n_copies,
6024                                                     size_t stride_tensor,
6025                                                     size_t stride_data) {
6026    auto sess = static_cast<ggml_hexagon_session *>(backend->context);
6027    HEX_VERBOSE("ggml-hex: %s get-tensor-2d-async %s : data %p offset %zu size %zu n_copies %zu stride_tensor %zu stride_data %zu usage %d\n",
6028                sess->c_name(), tensor->name, data, offset, size, n_copies, stride_tensor, stride_data, tensor->buffer ? (int) tensor->buffer->usage : -1);
6029    sess->flush_sync();
6030    if (sess->last_error > HTP_STATUS_OK) {
6031        GGML_ABORT("ggml-hex: %s get-tensor-2d-async failed : dsp-error %s\n", sess->c_name(), status_to_str(sess->last_error));
6032    }
6033    ggml_backend_tensor_get_2d(tensor, data, offset, size, n_copies, stride_tensor, stride_data);
6034}
6035
6036static struct ggml_backend_i hexagon_backend_i = {
6037    /* .get_name                = */ ggml_backend_hexagon_name,
6038    /* .free                    = */ ggml_backend_hexagon_free,
6039    /* .set_tensor_async        = */ ggml_backend_hexagon_set_tensor_async,
6040    /* .get_tensor_async        = */ ggml_backend_hexagon_get_tensor_async,
6041    /* .set_tensor_2d_async     = */ ggml_backend_hexagon_set_tensor_2d_async,
6042    /* .get_tensor_2d_async     = */ ggml_backend_hexagon_get_tensor_2d_async,
6043    /* .cpy_tensor_async        = */ ggml_backend_hexagon_cpy_tensor_async,
6044    /* .synchronize             = */ ggml_backend_hexagon_synchronize,
6045    /* .graph_plan_create       = */ NULL,
6046    /* .graph_plan_free         = */ NULL,
6047    /* .graph_plan_update       = */ NULL,
6048    /* .graph_plan_compute      = */ NULL,
6049    /* .graph_compute           = */ ggml_backend_hexagon_graph_compute,
6050    /* .event_record            = */ ggml_backend_hexagon_event_record,
6051    /* .event_wait              = */ ggml_backend_hexagon_event_wait,
6052    /* .graph_optimize          = */ ggml_backend_hexagon_graph_optimize,
6053};
6054
6055static ggml_guid_t ggml_backend_hexagon_guid() {
6056    static ggml_guid guid = { 0x7b, 0x57, 0xdc, 0xaf, 0xde, 0x12, 0x1d, 0x49,
6057                              0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 };
6058    return &guid;
6059}
6060
6061bool ggml_backend_is_hexagon(ggml_backend_t backend) {
6062    return backend && backend->iface.get_name == ggml_backend_hexagon_name;
6063}
6064
6065// device interface
6066
6067static ggml_backend_t ggml_backend_hexagon_device_init(ggml_backend_dev_t dev, const char * params) {
6068    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6069    auto sess    = dev_ctx->session();
6070
6071    return new ggml_backend{
6072        /* .guid      = */ ggml_backend_hexagon_guid(),
6073        /* .interface = */ hexagon_backend_i,
6074        /* .device    = */ dev,
6075        /* .context   = */ sess,
6076    };
6077
6078    GGML_UNUSED(params);
6079}
6080
6081static const char * ggml_backend_hexagon_device_get_name(ggml_backend_dev_t dev) {
6082    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6083    return dev_ctx->c_name();
6084
6085    GGML_UNUSED(dev);
6086}
6087
6088static const char * ggml_backend_hexagon_device_get_description(ggml_backend_dev_t dev) {
6089    return "Hexagon";
6090    GGML_UNUSED(dev);
6091}
6092
6093static void ggml_backend_hexagon_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) {
6094    *free  = 0;
6095    *total = *free;
6096
6097    GGML_UNUSED(dev);
6098}
6099
6100static enum ggml_backend_dev_type ggml_backend_hexagon_device_get_type(ggml_backend_dev_t dev) {
6101    return GGML_BACKEND_DEVICE_TYPE_GPU;
6102
6103    GGML_UNUSED(dev);
6104}
6105
6106static void ggml_backend_hexagon_device_get_props(ggml_backend_dev_t dev, struct ggml_backend_dev_props * props) {
6107    props->name        = ggml_backend_hexagon_device_get_name(dev);
6108    props->description = ggml_backend_hexagon_device_get_description(dev);
6109    props->type        = ggml_backend_hexagon_device_get_type(dev);
6110    ggml_backend_hexagon_device_get_memory(dev, &props->memory_free, &props->memory_total);
6111    props->caps = {
6112        /* .async                 = */ true,
6113        /* .host_buffer           = */ false,
6114        /* .buffer_from_host_ptr  = */ false,
6115        /* .events                = */ true,
6116        /* .mmap_support          = */ false,
6117    };
6118}
6119
6120static ggml_backend_buffer_type_t ggml_backend_hexagon_device_get_buffer_type(ggml_backend_dev_t dev) {
6121    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6122    return &dev_ctx->buffer_type;
6123}
6124
6125static ggml_backend_buffer_type_t ggml_backend_hexagon_device_get_host_buffer_type(ggml_backend_dev_t dev) {
6126    if (!opt_hostbuf) {
6127        return NULL;
6128    }
6129    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6130    return &dev_ctx->host_buffer_type;
6131}
6132
6133static bool ggml_hexagon_supported_cpy(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
6134    GGML_UNUSED(sess);
6135
6136    const struct ggml_tensor * src0 = op->src[0];
6137    const struct ggml_tensor * dst  = op;
6138
6139    // for now we can do f32 -> f16 and f16 -> f32 (without reshaping)
6140    if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) return false;
6141    if ( dst->type != GGML_TYPE_F32 &&  dst->type != GGML_TYPE_F16) return false;
6142
6143    const bool sametype   = (src0->type == dst->type);
6144    const bool transposed = ggml_is_transposed(src0) || ggml_is_transposed(dst);
6145    const bool sameshape  = !transposed && ggml_are_same_shape(src0, dst);
6146
6147    // can handle any shape and any same-type (pretty slow if reshaping is required)
6148    if (sametype) return true;
6149
6150    // cannot handle re-shaping and type conversion at the same time
6151    if (!sameshape) return false;
6152
6153    return true;
6154}
6155
6156static bool ggml_hexagon_supported_cont(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
6157    GGML_UNUSED(sess);
6158    const struct ggml_tensor * src0 = op->src[0];
6159
6160    // CONT is same-type only, supports f32 and f16
6161    if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) return false;
6162
6163    return true;
6164}
6165
6166static bool ggml_hexagon_supported_repeat(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
6167    GGML_UNUSED(sess);
6168    const struct ggml_tensor * src0 = op->src[0];
6169    const struct ggml_tensor * dst  = op;
6170
6171    // Support f32 and f16
6172    if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) return false;
6173
6174    // src and dst must be the same type
6175    if (src0->type != dst->type) return false;
6176
6177    // dst dims must be multiples of src dims
6178    if (dst->ne[0] % src0->ne[0] != 0) return false;
6179    if (dst->ne[1] % src0->ne[1] != 0) return false;
6180    if (dst->ne[2] % src0->ne[2] != 0) return false;
6181    if (dst->ne[3] % src0->ne[3] != 0) return false;
6182
6183    // require contiguous tensors (no transposition)
6184    if (ggml_is_transposed(src0) || ggml_is_transposed(dst)) return false;
6185
6186    return true;
6187}
6188
6189static bool ggml_hexagon_supported_concat(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
6190    int dim = ((const int32_t *) op->op_params)[0];
6191    if (dim < 0 || dim >= GGML_MAX_DIMS) {
6192        return false;
6193    }
6194
6195    for (int i = 0; i < GGML_MAX_SRC; ++i) {
6196        const struct ggml_tensor * src = op->src[i];
6197        if (!src) {
6198            continue;
6199        }
6200        if (src->type != GGML_TYPE_F32 && src->type != GGML_TYPE_I32 && src->type != GGML_TYPE_F16) {
6201            return false;
6202        }
6203    }
6204
6205    return true;
6206    GGML_UNUSED(sess);
6207}
6208
6209static bool ggml_hexagon_supported_fill(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
6210    const struct ggml_tensor * dst = op;
6211
6212    if (dst->type != GGML_TYPE_F32 && dst->type != GGML_TYPE_F16) {
6213        return false;
6214    }
6215
6216    return true;
6217    GGML_UNUSED(sess);
6218}
6219
6220static bool ggml_backend_hexagon_device_supports_op(ggml_backend_dev_t dev, const struct ggml_tensor * op) {
6221    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6222    auto sess    = dev_ctx->session();
6223
6224    // reject ops that match the filter
6225    if (opt_opfilter && std::regex_match(ggml_op_desc(op), *opt_opfilter)) {
6226        return false;
6227    }
6228
6229    bool supp = false;
6230    switch (op->op) {
6231        case GGML_OP_NONE:
6232        case GGML_OP_RESHAPE:
6233        case GGML_OP_VIEW:
6234        case GGML_OP_PERMUTE:
6235        case GGML_OP_TRANSPOSE:
6236            supp = true;
6237            break;
6238
6239        case GGML_OP_MUL:
6240        case GGML_OP_ADD:
6241        case GGML_OP_SUB:
6242        case GGML_OP_DIV:
6243            supp = ggml_hexagon_supported_binary(sess, op);
6244            break;
6245
6246        case GGML_OP_MUL_MAT:
6247            supp = ggml_hexagon_supported_mul_mat(sess, op);
6248            break;
6249
6250        case GGML_OP_MUL_MAT_ID:
6251            supp = ggml_hexagon_supported_mul_mat_id(sess, op);
6252            break;
6253
6254        case GGML_OP_ADD_ID:
6255            supp = ggml_hexagon_supported_add_id(sess, op);
6256            break;
6257
6258        case GGML_OP_NORM:
6259        case GGML_OP_L2_NORM:
6260        case GGML_OP_RMS_NORM:
6261        case GGML_OP_SCALE:
6262        case GGML_OP_CLAMP:
6263        case GGML_OP_LEAKY_RELU:
6264            supp = ggml_hexagon_supported_unary(sess, op);
6265            break;
6266
6267        case GGML_OP_SQR:
6268        case GGML_OP_SQRT:
6269        case GGML_OP_LOG:
6270            supp = ggml_hexagon_supported_unary(sess, op);
6271            break;
6272
6273        case GGML_OP_SUM_ROWS:
6274            supp = ggml_hexagon_supported_sum_rows(sess, op);
6275            break;
6276
6277        case GGML_OP_SOFT_MAX:
6278            supp = ggml_hexagon_supported_softmax(sess, op);
6279            break;
6280
6281        case GGML_OP_UNARY:
6282            switch (ggml_get_unary_op(op)) {
6283                case GGML_UNARY_OP_NEG:
6284                case GGML_UNARY_OP_EXP:
6285                case GGML_UNARY_OP_SIGMOID:
6286                case GGML_UNARY_OP_SOFTPLUS:
6287                case GGML_UNARY_OP_TANH:
6288                case GGML_UNARY_OP_ABS:
6289                case GGML_UNARY_OP_SILU:
6290                case GGML_UNARY_OP_GELU:
6291                case GGML_UNARY_OP_GELU_QUICK:
6292                case GGML_UNARY_OP_RELU:
6293                    supp = ggml_hexagon_supported_unary(sess, op);
6294                    break;
6295                default:
6296                    supp = false;
6297                    break;
6298            }
6299            break;
6300
6301        case GGML_OP_GLU:
6302            switch (ggml_get_glu_op(op)) {
6303                case GGML_GLU_OP_SWIGLU:
6304                case GGML_GLU_OP_SWIGLU_OAI:
6305                case GGML_GLU_OP_SWIGLU_CLAMP:
6306                case GGML_GLU_OP_GEGLU:
6307                    supp = ggml_hexagon_supported_activations(sess, op);
6308                    break;
6309                default:
6310                    supp = false;
6311                    break;
6312            }
6313            break;
6314
6315        case GGML_OP_ROPE:
6316            supp = ggml_hexagon_supported_rope(sess, op);
6317            break;
6318
6319        case GGML_OP_FLASH_ATTN_EXT:
6320            supp = ggml_hexagon_supported_flash_attn_ext(sess, op);
6321            break;
6322
6323        case GGML_OP_SET_ROWS:
6324            supp = ggml_hexagon_supported_set_rows(sess, op);
6325            break;
6326
6327        case GGML_OP_GET_ROWS:
6328            supp = ggml_hexagon_supported_get_rows(sess, op);
6329            break;
6330
6331        case GGML_OP_CPY:
6332            supp = ggml_hexagon_supported_cpy(sess, op);
6333            break;
6334
6335        case GGML_OP_CONT:
6336            supp = ggml_hexagon_supported_cont(sess, op);
6337            break;
6338
6339        case GGML_OP_REPEAT:
6340            supp = ggml_hexagon_supported_repeat(sess, op);
6341            break;
6342
6343        case GGML_OP_ARGSORT:
6344            supp = ggml_hexagon_supported_argsort(sess, op);
6345            break;
6346
6347        case GGML_OP_SSM_CONV:
6348            supp = ggml_hexagon_supported_ssm_conv(sess, op);
6349            break;
6350
6351        case GGML_OP_IM2COL:
6352            supp = ggml_hexagon_supported_im2col(sess, op);
6353            break;
6354
6355        case GGML_OP_GATED_DELTA_NET:
6356            supp = ggml_hexagon_supported_gated_delta_net(sess, op);
6357            break;
6358
6359        case GGML_OP_CUMSUM:
6360            supp = ggml_hexagon_supported_cumsum(sess, op);
6361            break;
6362
6363        case GGML_OP_CONCAT:
6364            supp = ggml_hexagon_supported_concat(sess, op);
6365            break;
6366
6367        case GGML_OP_FILL:
6368            supp = ggml_hexagon_supported_fill(sess, op);
6369            break;
6370
6371        case GGML_OP_DIAG:
6372            supp = ggml_hexagon_supported_diag(sess, op);
6373            break;
6374
6375        case GGML_OP_SOLVE_TRI:
6376            supp = ggml_hexagon_supported_solve_tri(sess, op);
6377            break;
6378
6379        case GGML_OP_TRI:
6380            supp = ggml_hexagon_supported_tri(sess, op);
6381            break;
6382
6383        case GGML_OP_PAD:
6384            supp = ggml_hexagon_supported_pad(sess, op);
6385            break;
6386
6387        default:
6388            break;
6389    }
6390
6391    ggml_hexagon_dump_op_supp(sess->name, op, supp);
6392    return supp;
6393}
6394
6395static bool ggml_backend_hexagon_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) {
6396    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6397
6398    bool supp = (buft == &dev_ctx->host_buffer_type) || (buft == &dev_ctx->buffer_type);
6399
6400    HEX_VERBOSE("ggml-hex: %s device-supports-buft %s %s\n", dev_ctx->c_name(), ggml_backend_buft_name(buft), supp ? "yes" : "no");
6401    return supp;
6402}
6403
6404static const struct ggml_backend_device_i ggml_backend_hexagon_device_i = {
6405    /* .get_name             = */ ggml_backend_hexagon_device_get_name,
6406    /* .get_description      = */ ggml_backend_hexagon_device_get_description,
6407    /* .get_memory           = */ ggml_backend_hexagon_device_get_memory,
6408    /* .get_type             = */ ggml_backend_hexagon_device_get_type,
6409    /* .get_props            = */ ggml_backend_hexagon_device_get_props,
6410    /* .init_backend         = */ ggml_backend_hexagon_device_init,
6411    /* .get_buffer_type      = */ ggml_backend_hexagon_device_get_buffer_type,
6412    /* .get_host_buffer_type = */ ggml_backend_hexagon_device_get_host_buffer_type,
6413    /* .buffer_from_host_ptr = */ NULL,  // ggml_backend_hexagon_device_buffer_from_ptr,
6414    /* .supports_op          = */ ggml_backend_hexagon_device_supports_op,
6415    /* .supports_buft        = */ ggml_backend_hexagon_device_supports_buft,
6416    /* .offload_op           = */ NULL,  // ggml_backend_hexagon_device_offload_op,
6417    /* .event_new            = */ ggml_backend_hexagon_device_event_new,
6418    /* .event_free           = */ ggml_backend_hexagon_device_event_free,
6419    /* .event_synchronize    = */ ggml_backend_hexagon_device_event_synchronize,
6420};
6421
6422//** backend registry
6423
6424ggml_hexagon_registry::ggml_hexagon_registry(ggml_backend_reg_t reg) {
6425    GGML_LOG_INFO("ggml-hex: Hexagon backend (experimental) : allocating new registry : ndev %zu\n", opt_ndev);
6426
6427    GGML_LOG_INFO("ggml-hex: Hexagon Arch version v%d\n", opt_arch);
6428
6429    // Create devices
6430    for (size_t i = 0; i < opt_ndev; i++) {
6431        const auto & cfg = opt_device_configs[i];
6432        if (cfg.mdev_group.empty()) {
6433            GGML_LOG_INFO("ggml-hex: device %zu: %s (phys=%d, virt=%d, domain=%s:%d)\n",
6434                          i, cfg.name.c_str(), cfg.physical_idx, cfg.virtual_idx, cfg.domain_name.c_str(), cfg.domain_id);
6435        } else {
6436            std::string peers_str;
6437            for (const auto & p : cfg.mdev_group) {
6438                if (!peers_str.empty()) peers_str += ", ";
6439                peers_str += p.name + " (phys=" + std::to_string(p.physical_idx) + ")";
6440            }
6441            GGML_LOG_INFO("ggml-hex: device %zu: %s (phys=%d, virt=%d, domain=%s:%d) [mdev peers: %s]\n",
6442                          i, cfg.name.c_str(), cfg.physical_idx, cfg.virtual_idx, cfg.domain_name.c_str(), cfg.domain_id, peers_str.c_str());
6443        }
6444        devices[i].iface   = ggml_backend_hexagon_device_i;
6445        devices[i].reg     = reg;
6446        devices[i].context = new ggml_backend_hexagon_device_context(i, opt_device_configs[i], &devices[i]);
6447    }
6448
6449}
6450
6451ggml_hexagon_registry::~ggml_hexagon_registry() {
6452    GGML_LOG_INFO("ggml-hex: releasing registry\n");
6453
6454    // Release devices
6455    for (size_t i = 0; i < opt_ndev; i++) {
6456        auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(devices[i].context);
6457        delete dev_ctx;
6458    }
6459}
6460
6461static const char * ggml_backend_hexagon_reg_get_name(ggml_backend_reg_t reg) {
6462    return "HTP";
6463    GGML_UNUSED(reg);
6464}
6465
6466static size_t ggml_backend_hexagon_reg_get_device_count(ggml_backend_reg_t reg) {
6467    return opt_ndev;
6468    GGML_UNUSED(reg);
6469}
6470
6471static ggml_backend_dev_t ggml_backend_hexagon_reg_get_device(ggml_backend_reg_t reg, size_t index) {
6472    auto hreg = static_cast<ggml_hexagon_registry *>(reg->context);
6473
6474    if (index >= opt_ndev || !hreg->devices[index].context) {
6475        return nullptr;
6476    }
6477
6478    return &hreg->devices[index];
6479}
6480
6481// ** communication context for tensor-split allreduce
6482
6483static void * ggml_backend_hexagon_comm_init(ggml_backend_t * backends, size_t n_backends) {
6484    if (n_backends < 2 || n_backends > 4) {
6485        return nullptr;
6486    }
6487
6488    for (size_t i = 0; i < n_backends; ++i) {
6489        if (!ggml_backend_is_hexagon(backends[i])) {
6490            return nullptr;
6491        }
6492    }
6493
6494    for (size_t i = 0; i < n_backends; i++) {
6495        auto sess_i = static_cast<ggml_hexagon_session *>(backends[i]->context);
6496        for (size_t j = i + 1; j < n_backends; j++) {
6497            auto sess_j = static_cast<ggml_hexagon_session *>(backends[j]->context);
6498            if (sess_i->phys_idx == sess_j->phys_idx) {
6499                return nullptr;
6500            }
6501        }
6502    }
6503
6504    auto * ctx = new ggml_backend_hexagon_comm_context();
6505    ctx->backends.assign(backends, backends + n_backends);
6506    ctx->n_backends = n_backends;
6507
6508    static ggml_hexagon_tensor_extra fence_extra { {}, 0, GGML_HEXAGON_TENSOR_FENCE };
6509    for (size_t i = 0; i < n_backends; i++) {
6510        auto sess_i = static_cast<ggml_hexagon_session *>(backends[i]->context);
6511        ctx->fence_slots[i] = (volatile uint32_t *) sess_i->alloc_fence(1);
6512        ctx->fence_tensors[i] = {};
6513        ctx->fence_tensors[i].buffer = &sess_i->fence_buf->backend_buffer;
6514        ctx->fence_tensors[i].extra  = &fence_extra;
6515        ctx->fence_tensors[i].data   = (void *) ctx->fence_slots[i];
6516        ctx->fence_tensors[i].type   = GGML_TYPE_I32;
6517        ctx->fence_tensors[i].ne[0]  = 4;
6518        ctx->fence_tensors[i].ne[1]  = 1;
6519        ctx->fence_tensors[i].ne[2]  = 1;
6520        ctx->fence_tensors[i].ne[3]  = 1;
6521        ctx->fence_tensors[i].nb[0]  = sizeof(int32_t);
6522        ctx->fence_tensors[i].nb[1]  = sizeof(int32_t);
6523        ctx->fence_tensors[i].nb[2]  = sizeof(int32_t);
6524        ctx->fence_tensors[i].nb[3]  = sizeof(int32_t);
6525        ctx->fence_tensors[i].op     = GGML_OP_NONE;
6526    }
6527
6528    return ctx;
6529}
6530
6531static void ggml_backend_hexagon_comm_free(void * comm_ctx_v) {
6532    if (!comm_ctx_v) return;
6533    auto * ctx = static_cast<ggml_backend_hexagon_comm_context *>(comm_ctx_v);
6534    for (size_t i = 0; i < ctx->n_backends; i++) {
6535        auto sess_i = static_cast<ggml_hexagon_session *>(ctx->backends[i]->context);
6536        sess_i->free_fence((void *) ctx->fence_slots[i], 1);
6537    }
6538    delete ctx;
6539}
6540
6541static bool ggml_backend_hexagon_comm_allreduce_tensor(void * comm_ctx_v, struct ggml_tensor ** tensors) {
6542    if (opt_ar_select == 0 || !comm_ctx_v) return false;
6543    auto * comm_ctx = static_cast<ggml_backend_hexagon_comm_context *>(comm_ctx_v);
6544    const size_t n_backends = comm_ctx->n_backends;
6545
6546    if (n_backends < 2 || n_backends > 4) return false;
6547
6548    for (size_t i = 0; i < n_backends; i++) {
6549        auto sess_i = static_cast<ggml_hexagon_session *>(comm_ctx->backends[i]->context);
6550        for (size_t j = i + 1; j < n_backends; j++) {
6551            auto sess_j = static_cast<ggml_hexagon_session *>(comm_ctx->backends[j]->context);
6552            if (sess_i->phys_idx == sess_j->phys_idx) {
6553                return false;
6554            }
6555        }
6556    }
6557
6558    for (size_t i = 0; i < n_backends; i++) {
6559        if (!tensors[i] || !tensors[i]->buffer || !ggml_backend_buffer_is_hexagon(tensors[i]->buffer)) {
6560            return false;
6561        }
6562        if (tensors[i]->type != tensors[0]->type) {
6563            return false;
6564        }
6565        if (!ggml_is_contiguous(tensors[i])) {
6566            return false;
6567        }
6568        if (ggml_nelements(tensors[i]) != ggml_nelements(tensors[0])) {
6569            return false;
6570        }
6571    }
6572
6573    if (tensors[0]->type != GGML_TYPE_F16 && tensors[0]->type != GGML_TYPE_F32) {
6574        return false;
6575    }
6576
6577    for (size_t r = 0; r < n_backends; r++) {
6578        auto sess = static_cast<ggml_hexagon_session *>(comm_ctx->backends[r]->context);
6579        struct htp_allreduce_kernel_params kparams;
6580        if (!ggml_hexagon_precompute_allreduce_params(sess, tensors[r], (uint32_t) r, (uint32_t) n_backends, false, false, &kparams)) {
6581            return false;
6582        }
6583    }
6584
6585    uint32_t max_seq = static_cast<ggml_hexagon_session *>(comm_ctx->backends[0]->context)->fence_seq;
6586    for (size_t i = 1; i < n_backends; i++) {
6587        auto sess_i = static_cast<ggml_hexagon_session *>(comm_ctx->backends[i]->context);
6588        if ((int32_t)(sess_i->fence_seq - max_seq) > 0) {
6589            max_seq = sess_i->fence_seq;
6590        }
6591    }
6592    if (++max_seq == 0) max_seq = 1;
6593    uint32_t fence_seq_entry = max_seq;
6594    if (++max_seq == 0) max_seq = 1;
6595    uint32_t fence_seq_exit  = max_seq;
6596
6597    for (size_t i = 0; i < n_backends; i++) {
6598        auto sess_i = static_cast<ggml_hexagon_session *>(comm_ctx->backends[i]->context);
6599        sess_i->fence_seq = max_seq;
6600    }
6601
6602    std::vector<const ggml_tensor *> data_tensors(n_backends);
6603    std::vector<const ggml_tensor *> sync_tensors(n_backends);
6604    for (size_t i = 0; i < n_backends; i++) {
6605        data_tensors[i] = tensors[i];
6606        sync_tensors[i] = &comm_ctx->fence_tensors[i];
6607    }
6608
6609    for (size_t r = 0; r < n_backends; r++) {
6610        auto sess = static_cast<ggml_hexagon_session *>(comm_ctx->backends[r]->context);
6611        sess->enqueue_allreduce(tensors[r], data_tensors, sync_tensors, (uint32_t) r, (uint32_t) n_backends, fence_seq_entry, fence_seq_exit);
6612        for (size_t j = 0; j < n_backends; j++) {
6613            if (r != j) {
6614                sess->add_peer(static_cast<ggml_hexagon_session *>(comm_ctx->backends[j]->context));
6615            }
6616        }
6617    }
6618
6619    return true;
6620}
6621
6622static ggml_backend_buffer_type_t ggml_backend_hexagon_split_buffer_type(int main_device, const float * tensor_split) {
6623    GGML_UNUSED(tensor_split);
6624    auto reg = ggml_backend_hexagon_reg();
6625    auto dev = ggml_backend_reg_dev_get(reg, main_device);
6626    if (!dev) {
6627        dev = ggml_backend_reg_dev_get(reg, 0);
6628    }
6629    if (!dev) return nullptr;
6630    auto dev_ctx = static_cast<ggml_backend_hexagon_device_context *>(dev->context);
6631    return &dev_ctx->buffer_type;
6632}
6633
6634static void * ggml_backend_hexagon_get_proc_address(ggml_backend_reg_t reg, const char * name) {
6635    GGML_UNUSED(reg);
6636    if (strcmp(name, "ggml_backend_split_buffer_type") == 0) {
6637        return (void *) ggml_backend_hexagon_split_buffer_type;
6638    }
6639    if (strcmp(name, "ggml_backend_comm_init") == 0) {
6640        return (void *) ggml_backend_hexagon_comm_init;
6641    }
6642    if (strcmp(name, "ggml_backend_comm_free") == 0) {
6643        return (void *) ggml_backend_hexagon_comm_free;
6644    }
6645    if (strcmp(name, "ggml_backend_comm_allreduce_tensor") == 0) {
6646        return (void *) ggml_backend_hexagon_comm_allreduce_tensor;
6647    }
6648    return NULL;
6649}
6650
6651template<typename T> std::vector<T> str_to_vec(const char* str) {
6652    std::stringstream ss(str);
6653    std::vector<T> v;
6654    std::string    t;
6655
6656    while (std::getline(ss, t, ',')) {
6657        v.push_back(std::stoul(t, nullptr, 0));
6658    }
6659
6660    return v;
6661}
6662
6663template<typename T, int BASE=10> std::string vec_to_str(std::vector<T> v) {
6664    std::stringstream ss;
6665    ss << std::setbase(BASE) << std::showbase;
6666    for (auto i : v) { ss << i << ','; }
6667    auto str = ss.str(); str.pop_back(); // drop last comma
6668    return str;
6669}
6670
6671static void ggml_hexagon_resolve_device_domain(ggml_hexagon_device_config & cfg, bool discovery_supported, const std::unordered_map<int, fastrpc_domain> & cdsp_map) {
6672    if (discovery_supported) {
6673        auto it = cdsp_map.find(cfg.physical_idx);
6674        if (it != cdsp_map.end()) {
6675            cfg.domain_id   = it->second.id;
6676            cfg.domain_name = it->second.name;
6677        } else {
6678            GGML_LOG_ERROR("ggml-hex: physical CDSP core %d not found on device (%zu CDSP core(s) available)\n",
6679                           cfg.physical_idx, cdsp_map.size());
6680            cfg.domain_id   = -1;
6681            cfg.domain_name = "";
6682        }
6683    } else {
6684        switch (cfg.physical_idx) {
6685            case 0:
6686                cfg.domain_id   = 3;
6687                cfg.domain_name = CDSP_DOMAIN_NAME;
6688                break;
6689            case 1:
6690                cfg.domain_id   = 4;
6691                cfg.domain_name = "cdsp1";
6692                break;
6693            default:
6694                GGML_LOG_ERROR("ggml-hex: physical CDSP core %d not supported without dynamic discovery\n",
6695                               cfg.physical_idx);
6696                cfg.domain_id   = -1;
6697                cfg.domain_name = "";
6698                break;
6699        }
6700    }
6701    for (auto & sub_cfg : cfg.mdev_group) {
6702        ggml_hexagon_resolve_device_domain(sub_cfg, discovery_supported, cdsp_map);
6703    }
6704}
6705
6706// Enumerate NPU (aka CDSP) domains via FASTRPC_GET_DOMAINS if supported,
6707// and populate domain_id and domain_name for all configured devices.
6708static void ggml_hexagon_discover_devices() {
6709    std::unordered_map<int, fastrpc_domain> cdsp_map;
6710    bool discovery_supported = false;
6711
6712    system_req_payload domain_info = {};
6713    domain_info.id              = FASTRPC_GET_DOMAINS;
6714    domain_info.sys.domains     = nullptr;
6715    domain_info.sys.max_domains = 0;
6716    domain_info.sys.flags       = DOMAINS_LIST_FLAGS_SET_TYPE(0, FASTRPC_NSP);
6717
6718    int err = remote_system_request(&domain_info);
6719    if (err == AEE_SUCCESS && domain_info.sys.num_domains > 0) {
6720        std::vector<fastrpc_domain> domains(domain_info.sys.num_domains);
6721        domain_info.sys.domains     = domains.data();
6722        domain_info.sys.max_domains = (int) domains.size();
6723
6724        err = remote_system_request(&domain_info);
6725        if (err == AEE_SUCCESS) {
6726            discovery_supported = true;
6727            const int n_domains = std::min(domain_info.sys.num_domains, (int) domains.size());
6728            for (int i = 0; i < n_domains; i++) {
6729                GGML_LOG_INFO("ggml-hex: FASTRPC_GET_DOMAINS[%d]: type %d id %d name '%s' status %d instance-id %d\n",
6730                              i, (int) domains[i].type, domains[i].id, domains[i].name, domains[i].status, domains[i].instance_id);
6731                if (domains[i].type != FASTRPC_NSP) {
6732                    GGML_LOG_DEBUG("ggml-hex:   skipping non-CDSP domain (type=%d)\n", (int) domains[i].type);
6733                    continue;
6734                }
6735                if (!domains[i].status) {
6736                    GGML_LOG_WARN("ggml-hex:   skipping CDSP domain id=%d (status=down)\n", domains[i].id);
6737                    continue;
6738                }
6739                cdsp_map[domains[i].instance_id] = domains[i];
6740                GGML_LOG_INFO("ggml-hex: using CDSP domain: instance-id %d id %d name '%s'\n",
6741                              domains[i].instance_id, domains[i].id, domains[i].name);
6742            }
6743        } else {
6744            GGML_LOG_WARN("ggml-hex: FASTRPC_GET_DOMAINS fetch failed (0x%x), using static CDSP domains\n", (unsigned) err);
6745        }
6746    } else if (err != AEE_SUCCESS) {
6747        GGML_LOG_DEBUG("ggml-hex: FASTRPC_GET_DOMAINS query failed (0x%x), using static CDSP domains\n", (unsigned) err);
6748    }
6749
6750    // Populate domain IDs and names for all configured devices
6751    for (size_t i = 0; i < opt_ndev; i++) {
6752        ggml_hexagon_resolve_device_domain(opt_device_configs[i], discovery_supported, cdsp_map);
6753    }
6754}
6755
6756static void ggml_hexagon_init(ggml_backend_reg * reg) {
6757    // Basic sanity checks to make sure definitions match
6758    static_assert((unsigned int) HTP_TYPE_Q4_0 == (unsigned int) GGML_TYPE_Q4_0,
6759                  "please update hexagon_type to match ggml_type");
6760    static_assert((unsigned int) HTP_TYPE_Q4_1 == (unsigned int) GGML_TYPE_Q4_1,
6761                  "please update hexagon_type to match ggml_type");
6762    static_assert((unsigned int) HTP_TYPE_Q8_0 == (unsigned int) GGML_TYPE_Q8_0,
6763                  "please update hexagon_type to match ggml_type");
6764    static_assert((unsigned int) HTP_TYPE_MXFP4 == (unsigned int) GGML_TYPE_MXFP4,
6765                  "please update hexagon_type to match ggml_type");
6766    static_assert((unsigned int) HTP_TYPE_IQ4_NL == (unsigned int) GGML_TYPE_IQ4_NL,
6767                  "please update hexagon_type to match ggml_type");
6768
6769    const char * str_verbose  = getenv("GGML_HEXAGON_VERBOSE");
6770    const char * str_opbatch  = getenv("GGML_HEXAGON_OPBATCH");
6771    const char * str_opqueue  = getenv("GGML_HEXAGON_OPQUEUE");
6772    const char * str_oppoll   = getenv("GGML_HEXAGON_OPPOLL");
6773    const char * str_opfusion = getenv("GGML_HEXAGON_OPFUSION");
6774    const char * str_opfilter = getenv("GGML_HEXAGON_OPFILTER");
6775    const char * str_profile  = getenv("GGML_HEXAGON_PROFILE");
6776    const char * str_etm      = getenv("GGML_HEXAGON_ETM");
6777    const char * str_nhvx     = getenv("GGML_HEXAGON_NHVX");
6778    const char * str_nhmx     = getenv("GGML_HEXAGON_NHMX");
6779    const char * str_mm_select = getenv("GGML_HEXAGON_MM_SELECT");
6780    const char * str_fa_select = getenv("GGML_HEXAGON_FA_SELECT");
6781    const char * str_ar_select = getenv("GGML_HEXAGON_AR_SELECT");
6782    const char * str_ndev     = getenv("GGML_HEXAGON_NDEV");
6783    const char * str_arch     = getenv("GGML_HEXAGON_ARCH");
6784    const char * str_vmem     = getenv("GGML_HEXAGON_VMEM");
6785    const char * str_mbuf     = getenv("GGML_HEXAGON_MBUF");
6786    const char * str_optrace  = getenv("GGML_HEXAGON_OPTRACE");
6787    const char * str_hostbuf  = getenv("GGML_HEXAGON_HOSTBUF");
6788
6789    // Init Arch first since it affects other defaults
6790    if (!str_arch) {
6791        int err = htpdrv_get_arch(CDSP_DOMAIN_ID, &opt_arch);
6792        if (err != 0) {
6793            GGML_LOG_ERROR("ggml-hex: failed to query HTP version (err %d) defaulting to v73\n", err);
6794            opt_arch = 73;
6795        } else {
6796            if (opt_arch < 73) {
6797                GGML_LOG_WARN("ggml-hex: Hexagon arch v%d is under supported range, capping at v73\n", opt_arch);
6798                opt_arch = 73;
6799            } else if (opt_arch > 81) {
6800                GGML_LOG_WARN("ggml-hex: Hexagon arch v%d is over supported range, capping at v81\n", opt_arch);
6801                opt_arch = 81;
6802            }
6803        }
6804    } else {
6805        if (str_arch[0] == 'v' || str_arch[0] == 'V') {
6806            str_arch++;
6807        }
6808        opt_arch = strtoul(str_arch, NULL, 0);
6809    }
6810
6811    size_t MiB = 1024 * 1024;
6812
6813    // Update vmem default
6814    opt_vmem = opt_arch >= 75 ? HTP_OP_MAX_VMEM_DEFAULT : 3000 * MiB;
6815
6816    auto RE_ICASE = std::regex_constants::icase;
6817
6818    opt_opfilter  = str_opfilter ? new std::regex(str_opfilter, RE_ICASE) : NULL;
6819    opt_verbose   = str_verbose  ? atoi(str_verbose)                      : 0;
6820    opt_opbatch   = str_opbatch  ? strtoul(str_opbatch, NULL, 0)          : opt_opbatch;
6821    opt_opqueue   = str_opqueue  ? strtoul(str_opqueue, NULL, 0)          : opt_opqueue;
6822    opt_optrace   = str_optrace  ? strtoul(str_optrace, NULL, 0)          : (opt_opbatch * 256);
6823    opt_oppoll    = str_oppoll   ? strtoul(str_oppoll,  NULL, 0)          : opt_oppoll;
6824    opt_opfusion  = str_opfusion ? atoi(str_opfusion)                     : opt_opfusion;
6825    opt_profile   = str_profile  ? atoi(str_profile)                      : 0;
6826    opt_etm       = str_etm      ? atoi(str_etm)                          : 0;
6827    opt_nhvx      = str_nhvx     ? strtoul(str_nhvx, NULL, 0)             : opt_nhvx;
6828    opt_nhmx      = str_nhmx     ? atoi(str_nhmx)                         : opt_nhmx;
6829    opt_mm_select = str_mm_select ? atoi(str_mm_select)                   : opt_mm_select;
6830    opt_fa_select = str_fa_select ? atoi(str_fa_select)                   : opt_fa_select;
6831    opt_ar_select = str_ar_select ? atoi(str_ar_select)                   : opt_ar_select;
6832    opt_mbuf      = str_mbuf     ? strtoul(str_mbuf, NULL, 0) * MiB       : opt_mbuf;
6833    opt_vmem      = str_vmem     ? strtoul(str_vmem, NULL, 0) * MiB       : opt_vmem;
6834    opt_hostbuf   = str_hostbuf  ? atoi(str_hostbuf) != 0                 : opt_hostbuf;
6835
6836    // Parse device configuration
6837    const char * str_devices  = getenv("GGML_HEXAGON_DEVICES");
6838    if (!str_devices && str_ndev && str_ndev[0] != '\0') {
6839        GGML_LOG_WARN("DEPRECATED: GGML_HEXAGON_NDEV is deprecated. use GGML_HEXAGON_DEVICES instead\n");
6840        str_devices = str_ndev;
6841    }
6842
6843    if (str_devices && str_devices[0] != '\0') {
6844        bool is_single_number = true;
6845        for (int i = 0; str_devices[i] != '\0'; i++) {
6846            if (!isdigit((unsigned char)str_devices[i])) {
6847                is_single_number = false;
6848                break;
6849            }
6850        }
6851        if (is_single_number) {
6852            int n = atoi(str_devices);
6853            if (n < 1) n = 1;
6854            if (n > GGML_HEXAGON_MAX_SESSIONS) n = GGML_HEXAGON_MAX_SESSIONS;
6855            opt_ndev = n;
6856            for (size_t i = 0; i < opt_ndev; i++) {
6857                opt_device_configs[i].physical_idx = 0;
6858                opt_device_configs[i].virtual_idx  = (int)i;
6859                opt_device_configs[i].name         = "HTP" + std::to_string(i);
6860                opt_device_configs[i].mdev_group.clear();
6861            }
6862        } else {
6863            std::string s_devices(str_devices);
6864            std::vector<std::string> items;
6865            std::string curr_item;
6866            int bracket_depth = 0;
6867            for (char ch : s_devices) {
6868                if (ch == '[') {
6869                    bracket_depth++;
6870                    curr_item += ch;
6871                } else if (ch == ']') {
6872                    if (bracket_depth > 0) bracket_depth--;
6873                    curr_item += ch;
6874                } else if (ch == ',' && bracket_depth == 0) {
6875                    size_t s = curr_item.find_first_not_of(" \t\r\n");
6876                    size_t e = curr_item.find_last_not_of(" \t\r\n");
6877                    if (s != std::string::npos) {
6878                        items.push_back(curr_item.substr(s, e - s + 1));
6879                    }
6880                    curr_item.clear();
6881                } else {
6882                    curr_item += ch;
6883                }
6884            }
6885            size_t s = curr_item.find_first_not_of(" \t\r\n");
6886            size_t e = curr_item.find_last_not_of(" \t\r\n");
6887            if (s != std::string::npos) {
6888                items.push_back(curr_item.substr(s, e - s + 1));
6889            }
6890
6891            opt_ndev = 0;
6892            for (const auto & item : items) {
6893                size_t b_open  = item.find('[');
6894                size_t b_close = item.rfind(']');
6895
6896                if (b_open != std::string::npos && b_close != std::string::npos && b_close > b_open) {
6897                    // Grouped / composite syntax: Name[phys_spec:virt] or Name[phys_spec]
6898                    std::string dev_name = item.substr(0, b_open);
6899                    std::string content  = item.substr(b_open + 1, b_close - b_open - 1);
6900
6901                    int virt = 0;
6902                    std::string phys_spec = content;
6903                    size_t colon_pos = content.find(':');
6904                    if (colon_pos != std::string::npos) {
6905                        phys_spec = content.substr(0, colon_pos);
6906                        try {
6907                            virt = std::stoi(content.substr(colon_pos + 1));
6908                        } catch (...) {
6909                            virt = 0;
6910                        }
6911                    } else {
6912                        size_t dev_colon = dev_name.find(':');
6913                        if (dev_colon != std::string::npos) {
6914                            try {
6915                                virt = std::stoi(dev_name.substr(dev_colon + 1));
6916                            } catch (...) {
6917                                virt = 0;
6918                            }
6919                        }
6920                    }
6921
6922                    // Parse physical indices from phys_spec (e.g. 0-1, 0,1, 0-3, etc.)
6923                    std::vector<int> phys_list;
6924                    std::stringstream pss(phys_spec);
6925                    std::string p_part;
6926                    while (std::getline(pss, p_part, ',')) {
6927                        size_t ps = p_part.find_first_not_of(" \t\r\n");
6928                        size_t pe = p_part.find_last_not_of(" \t\r\n");
6929                        if (ps == std::string::npos) continue;
6930                        p_part = p_part.substr(ps, pe - ps + 1);
6931
6932                        size_t dash_pos = p_part.find('-');
6933                        if (dash_pos != std::string::npos) {
6934                            try {
6935                                int p_start = std::stoi(p_part.substr(0, dash_pos));
6936                                int p_end   = std::stoi(p_part.substr(dash_pos + 1));
6937                                for (int p = p_start; p <= p_end; p++) {
6938                                    if (std::find(phys_list.begin(), phys_list.end(), p) == phys_list.end()) {
6939                                        phys_list.push_back(p);
6940                                    }
6941                                }
6942                            } catch (...) {
6943                                GGML_LOG_WARN("ggml-hex: failed to parse physical range in '%s'\n", p_part.c_str());
6944                            }
6945                        } else {
6946                            try {
6947                                int p = std::stoi(p_part);
6948                                if (std::find(phys_list.begin(), phys_list.end(), p) == phys_list.end()) {
6949                                    phys_list.push_back(p);
6950                                }
6951                            } catch (...) {
6952                                GGML_LOG_WARN("ggml-hex: failed to parse physical index in '%s'\n", p_part.c_str());
6953                            }
6954                        }
6955                    }
6956
6957                    if (phys_list.empty()) {
6958                        phys_list.push_back(0);
6959                    }
6960
6961                    if (opt_ndev < GGML_HEXAGON_MAX_SESSIONS) {
6962                        auto & cfg = opt_device_configs[opt_ndev];
6963                        cfg.name         = dev_name;
6964                        cfg.physical_idx = phys_list[0];
6965                        cfg.virtual_idx  = virt;
6966                        cfg.mdev_group.clear();
6967
6968                        for (size_t k = 1; k < phys_list.size(); k++) {
6969                            ggml_hexagon_device_config sub_cfg;
6970                            sub_cfg.physical_idx = phys_list[k];
6971                            sub_cfg.virtual_idx  = virt;
6972                            sub_cfg.name         = "HTP" + std::to_string(phys_list[k]) + ":" + std::to_string(virt);
6973                            cfg.mdev_group.push_back(sub_cfg);
6974                        }
6975                        opt_ndev++;
6976                    } else {
6977                        GGML_LOG_WARN("ggml-hex: max sessions limit reached (%d), ignoring device %s\n", GGML_HEXAGON_MAX_SESSIONS, item.c_str());
6978                    }
6979                } else if (item.rfind("HTP", 0) == 0) {
6980                    std::string rest = item.substr(3);
6981                    size_t colon_pos = rest.find(':');
6982                    int phys = 0;
6983                    int virt = 0;
6984                    try {
6985                        if (colon_pos == std::string::npos) {
6986                            phys = std::stoi(rest);
6987                            virt = 0;
6988                        } else {
6989                            phys = std::stoi(rest.substr(0, colon_pos));
6990                            virt = std::stoi(rest.substr(colon_pos + 1));
6991                        }
6992                    } catch (...) {
6993                        GGML_LOG_WARN("ggml-hex: failed to parse device index in '%s'\n", item.c_str());
6994                        continue;
6995                    }
6996
6997                    if (opt_ndev < GGML_HEXAGON_MAX_SESSIONS) {
6998                        opt_device_configs[opt_ndev].physical_idx = phys;
6999                        opt_device_configs[opt_ndev].virtual_idx  = virt;
7000                        opt_device_configs[opt_ndev].name         = colon_pos == std::string::npos
7001                            ? "HTP" + std::to_string(phys)
7002                            : "HTP" + std::to_string(phys) + ":" + std::to_string(virt);
7003                        opt_device_configs[opt_ndev].mdev_group.clear();
7004                        opt_ndev++;
7005                    } else {
7006                        GGML_LOG_WARN("ggml-hex: max sessions limit reached (%d), ignoring device %s\n", GGML_HEXAGON_MAX_SESSIONS, item.c_str());
7007                    }
7008                } else {
7009                    GGML_LOG_WARN("ggml-hex: invalid device name format '%s', must start with HTP\n", item.c_str());
7010                }
7011            }
7012        }
7013    } else {
7014        opt_ndev = 1;
7015        opt_device_configs[0].physical_idx = 0;
7016        opt_device_configs[0].virtual_idx  = 0;
7017        opt_device_configs[0].name         = "HTP0";
7018        opt_device_configs[0].mdev_group.clear();
7019    }
7020
7021#if defined(__ANDROID__)
7022    if (opt_arch < 75) {
7023        opt_ndev = 1;
7024        GGML_LOG_WARN("ggml-hex: forcing ndev to 1 for SoCs archs lower than v75.\n");
7025    }
7026#endif
7027
7028    // Resolve domain info for all configured devices
7029    ggml_hexagon_discover_devices();
7030
7031    if (str_profile) {
7032        opt_pmu_evt = [&]() -> std::vector<uint32_t> {
7033            auto v  = str_to_vec<uint32_t>(str_profile);
7034            switch (v.size()) {
7035                case 1:  opt_profile = v[0]; return opt_pmu_evt; // mode with default pmu events
7036                case 8:  opt_profile = 2;    return v;           // mode with custom  pmu events
7037                default: opt_profile = 0;    return {};          // garbage input
7038            }}();
7039        if (opt_profile == 1) opt_pmu_evt = {};
7040        GGML_LOG_INFO("ggml-hex: Profiling mode %u : pmu-evt [ %s ]\n", opt_profile,
7041                vec_to_str<uint32_t, 16>(opt_pmu_evt).c_str());
7042    }
7043
7044    reg->context = new ggml_hexagon_registry(reg);
7045}
7046
7047static const struct ggml_backend_reg_i ggml_backend_hexagon_reg_i = {
7048    /* .get_name         = */ ggml_backend_hexagon_reg_get_name,
7049    /* .get_device_count = */ ggml_backend_hexagon_reg_get_device_count,
7050    /* .get_device       = */ ggml_backend_hexagon_reg_get_device,
7051    /* .get_proc_address = */ ggml_backend_hexagon_get_proc_address,
7052};
7053
7054ggml_backend_reg_t ggml_backend_hexagon_reg(void) {
7055    static bool initialized = false;
7056
7057    static ggml_backend_reg reg = { /* .api_version = */ GGML_BACKEND_API_VERSION,
7058                                    /* .iface       = */ ggml_backend_hexagon_reg_i,
7059                                    /* .context     = */ NULL };
7060
7061    {
7062        static std::mutex           mutex;
7063        std::lock_guard<std::mutex> lock(mutex);
7064        if (!initialized) {
7065            auto nErr = htpdrv_init();
7066            if (nErr != AEE_SUCCESS) {
7067                return NULL;
7068            }
7069
7070            ggml_hexagon_init(&reg);
7071        }
7072
7073        initialized = true;
7074    }
7075
7076    return &reg;
7077}
7078
7079GGML_BACKEND_DL_IMPL(ggml_backend_hexagon_reg)