char-fork/llama.cpp

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

Aman Guptaggml: add SWIGLU_CLAMP (#27930)0190529ec

char/aliceai-t5-moe
86.8 KiB1880 linesraw
1#include "ggml-et.h"
2
3#include "ggml-backend-impl.h"
4#include "ggml-backend.h"
5#include "ggml-et-common.h"
6#include "ggml-et-kernels.h"
7#include "ggml-et-memops.h"
8#include "ggml-et-ops.h"
9#include "ggml-impl.h"
10#include "ggml.h"
11
12#include <stdarg.h>
13
14#include <cstdarg>
15#include <cstdio>
16#include <cstdlib>
17#include <cstring>
18#include <vector>
19
20#if __has_include(<filesystem>)
21#    include <filesystem>
22namespace fs = std::filesystem;
23#elif __has_include(<experimental/filesystem>)
24#    include <experimental/filesystem>
25namespace fs = std::experimental::filesystem;
26#else
27#    error "cannot include the filesystem library"
28#endif
29
30/*
31 * ggml_et_dump_tensor_metadata
32 * @brief prints the metadata of a single tensorf
33 */
34static void ggml_et_dump_tensor_metadata(const ggml_tensor * ggtensor, size_t indent_level, const char * title) {
35    char * spaces = (char *) alloca(indent_level + 1);
36    memset(spaces, ' ', indent_level);
37    spaces[indent_level] = '\0';
38    fprintf(stderr,
39            "%s%s: %s\n"
40            "%s  type: %s\n"
41            "%s  ne: %lld %lld %lld %lld\n"
42            "%s  nb: %zu %zu %zu %zu\n"
43            "%s  op: %s\n"
44            "%s  data: %p\n"
45            "%s  src0: %p\n",
46            spaces, title, ggtensor->name, spaces, ggml_type_name(ggtensor->type), spaces, (long long) ggtensor->ne[0],
47            (long long) ggtensor->ne[1], (long long) ggtensor->ne[2], (long long) ggtensor->ne[3], spaces,
48            ggtensor->nb[0], ggtensor->nb[1], ggtensor->nb[2], ggtensor->nb[3], spaces, ggml_op_name(ggtensor->op),
49            spaces, ggtensor->data, spaces, (void *) ggtensor->src[0]);
50}
51
52/*
53 * ggml_et_dump_operator_metadata
54 * @brief prints the metadata of a single tensor (or operator) including it's input and views
55 */
56static void ggml_et_dump_operator_metadata(const ggml_tensor * ggtensor) {
57    GGML_ASSERT(ggtensor != NULL);
58    ggml_et_dump_tensor_metadata(ggtensor, 0, "GGML tensor");
59    for (int i = 0; i < GGML_MAX_SRC && ggtensor->src[i]; i++) {
60        char arr[16];
61        int  n = snprintf(arr, sizeof(arr), "src[%i]->name", i);
62        GGML_ASSERT((unsigned) n < sizeof(arr) && "printed too much data to stack buffer");
63        ggml_et_dump_tensor_metadata(ggtensor->src[i], 2, arr);
64    }
65    if (ggtensor->view_src) {
66        ggml_et_dump_tensor_metadata(ggtensor, 2, "view_src");
67    }
68}
69
70static struct ggml_et_driver {
71    std::shared_ptr<dev::IDeviceLayer>                device_layer;
72    std::shared_ptr<rt::IRuntime>                     runtime;
73    std::unique_ptr<std::ofstream>                    profile_stream;
74    std::unique_ptr<std::ofstream>                    kernel_id_stream;
75    std::vector<std::pair<std::string, rt::KernelId>> kernel_map;
76    bool                                              profiling_enabled = false;
77} _drv;
78
79// Check at runtime environment variables for paths likely holding ET toolchain with sysemu elf files
80static std::string ggml_et_get_default_et_path() {
81    // List of environment variables to check in order of preference
82    const char * const env_vars[] = { "ET_TOOLCHAIN", "TOOLCHAIN_ROOT" };
83
84    for (const char * var : env_vars) {
85        if (const char * et_path = std::getenv(var)) {
86            if (et_path && *et_path != '\0') {
87                return fs::path(et_path).string();
88            }
89        }
90    }
91
92    // Otherwise assume default
93    return fs::path("/opt/et").string();
94}
95
96// config when using sysemu instead of PCIe hardware device
97// adapted from `ainekko/et-platform/esperanto-tools-libs/tools/src/bench.cpp`
98static inline auto ggml_et_get_default_sysemu_options() {
99    constexpr uint64_t kSysEmuMaxCycles        = std::numeric_limits<uint64_t>::max();
100    constexpr uint64_t kSysEmuMinionShiresMask = 0x1FFFFFFFFu;
101    const std::string  et_path                 = ggml_et_get_default_et_path() + "/";
102
103    emu::SysEmuOptions sysEmuOptions;
104
105    // Construct all paths
106    sysEmuOptions.bootromTrampolineToBL2ElfPath =
107        et_path + "lib/esperanto-fw/BootromTrampolineToBL2/BootromTrampolineToBL2.elf";
108    sysEmuOptions.spBL2ElfPath =
109        et_path + "lib/esperanto-fw/ServiceProcessorBL2/fast-boot/ServiceProcessorBL2_fast-boot.elf";
110    sysEmuOptions.machineMinionElfPath = et_path + "lib/esperanto-fw/MachineMinion/MachineMinion.elf";
111    sysEmuOptions.masterMinionElfPath  = et_path + "lib/esperanto-fw/MasterMinion/MasterMinion.elf";
112    sysEmuOptions.workerMinionElfPath  = et_path + "lib/esperanto-fw/WorkerMinion/WorkerMinion.elf";
113    sysEmuOptions.executablePath       = et_path + "bin/sys_emu";
114
115    // Check that each path has a valid existing non-zero file otherwise emulator just silently hangs
116    const std::vector<std::string> required_files = {
117        sysEmuOptions.bootromTrampolineToBL2ElfPath, sysEmuOptions.spBL2ElfPath,
118        sysEmuOptions.machineMinionElfPath,          sysEmuOptions.masterMinionElfPath,
119        sysEmuOptions.workerMinionElfPath,           sysEmuOptions.executablePath,
120    };
121
122    for (const auto & file : required_files) {
123        if (!fs::exists(file) || fs::file_size(file) == 0) {
124            // Check that each path has a valid existing non-zero file otherwise emulator just silently hangs
125            GGML_LOG_ERROR("ET: Unable to find required sysemu file: %s", file.c_str());
126            GGML_LOG_ERROR("ET: Confirm et-platform is correctly installed at configured path.");
127            abort();
128        }
129    }
130
131    sysEmuOptions.runDir           = (fs::current_path().string() + "/");
132    sysEmuOptions.maxCycles        = kSysEmuMaxCycles;
133    sysEmuOptions.minionShiresMask = kSysEmuMinionShiresMask;
134    sysEmuOptions.puUart0Path      = sysEmuOptions.runDir + "pu_uart0_tx.log";
135    sysEmuOptions.puUart1Path      = sysEmuOptions.runDir + "pu_uart1_tx.log";
136    sysEmuOptions.spUart0Path      = sysEmuOptions.runDir + "spio_uart0_tx.log";
137    sysEmuOptions.spUart1Path      = sysEmuOptions.runDir + "spio_uart1_tx.log";
138    sysEmuOptions.startGdb         = false;
139    sysEmuOptions.memcheck         = false;
140
141    return sysEmuOptions;
142}
143
144// Forward declaration
145static void ggml_et_driver_cleanup();
146
147static bool ggml_et_driver_init() {
148    if (_drv.runtime != nullptr) {
149        assert(_drv.device_layer != nullptr);
150    } else {
151        try {
152#if defined GGML_ET_SYSEMU && GGML_ET_SYSEMU
153            // For emulator device using sysEmuOptions provided by function above enabled compiling with `-DGGML_ET_SYSEMU=ON`
154            _drv.device_layer = dev::IDeviceLayer::createSysEmuDeviceLayer(ggml_et_get_default_sysemu_options());
155#else
156            // For physical PCIe device
157            _drv.device_layer = dev::IDeviceLayer::createPcieDeviceLayer();
158#endif  // GGML_ET_SYSEMU
159
160            _drv.runtime = rt::IRuntime::create(_drv.device_layer);
161
162            // Initialize profiler if requested via environment variable
163            const char * profile_path = getenv("GGML_ET_PROFILE");
164            if (profile_path) {
165                std::string output_path    = std::string(profile_path) + "/et_runtime_trace.json";
166                std::string kernel_id_path = std::string(profile_path) + "/kernel_id.json";
167
168                _drv.profile_stream   = std::make_unique<std::ofstream>(output_path);
169                _drv.kernel_id_stream = std::make_unique<std::ofstream>(kernel_id_path);
170                if (!_drv.profile_stream->is_open()) {
171                    GGML_LOG_ERROR("ET: Failed to open profiling output file: %s", output_path.c_str());
172                    abort();
173                }
174                if (!_drv.kernel_id_stream->is_open()) {
175                    GGML_LOG_ERROR("ET: Failed to open profiling kernel map: %s", kernel_id_path.c_str());
176                    abort();
177                }
178
179                auto * profiler = _drv.runtime->getProfiler();
180                profiler->start(*_drv.profile_stream, rt::IProfiler::OutputType::Json);
181                _drv.profiling_enabled = true;
182                GGML_LOG_INFO("ET: Runtime profiler started (JSON format)");
183
184                // Register cleanup at program exit
185                std::atexit(ggml_et_driver_cleanup);
186            }
187        } catch (const std::exception & e) {
188            GGML_LOG_ERROR("ggml_et: %s", e.what());
189            if (_drv.device_layer != nullptr) {
190                _drv.device_layer.reset();
191            }
192            if (_drv.runtime != nullptr) {
193                _drv.runtime.reset();
194            }
195            return false;
196        }
197    }
198    return true;
199}
200
201static std::shared_ptr<dev::IDeviceLayer> ggml_et_devicelayer() {
202    return _drv.device_layer;
203}
204
205std::shared_ptr<rt::IRuntime> ggml_et_runtime() {
206    return _drv.runtime;
207}
208
209static void ggml_et_driver_cleanup() {
210    if (_drv.profiling_enabled && _drv.runtime) {
211        GGML_LOG_INFO("ET: Stopping runtime profiler");
212        auto * profiler = _drv.runtime->getProfiler();
213        profiler->stop();
214        _drv.profiling_enabled = false;
215
216        if (_drv.profile_stream) {
217            _drv.profile_stream->close();
218            _drv.profile_stream.reset();
219        }
220
221        // Save kernel map
222        if (_drv.kernel_id_stream && !_drv.kernel_map.empty()) {
223            auto & os = *_drv.kernel_id_stream;
224            // XXX: Manual JSON construction. Not pretty but removes dependency
225            os << "{\n";
226            for (size_t i = 0; i < _drv.kernel_map.size(); i++) {
227                os << "  \"" << _drv.kernel_map[i].first << "\": " << (int) _drv.kernel_map[i].second;
228                if (i + 1 < _drv.kernel_map.size()) {
229                    os << ",";
230                }
231                os << "\n";
232            }
233            os << "}\n";
234            _drv.kernel_id_stream->close();
235            _drv.kernel_id_stream.reset();
236        }
237    }
238}
239
240static ggml_backend_dev_t ggml_backend_et_reg_get_device(ggml_backend_reg_t reg, size_t devidx);
241
242static void ggml_backend_et_buffer_free_buffer(ggml_backend_buffer_t buffer) {
243    ggml_backend_et_buffer_context * ctx = (ggml_backend_et_buffer_context *) buffer->context;
244    if (ctx->data != nullptr) {
245        std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
246        if (runtime) {
247            runtime->freeDevice(ctx->rtid, static_cast<std::byte *>(ctx->data));
248        }
249    }
250    delete ctx;
251}
252
253static void * ggml_backend_et_buffer_get_base(ggml_backend_buffer_t buffer) {
254    ggml_backend_et_buffer_context * ctx = (ggml_backend_et_buffer_context *) buffer->context;
255    return ctx->data;
256}
257
258static ggml_status ggml_backend_et_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
259    // View tensors share buffer with their view_src, no additional initialization needed
260    if (tensor->view_src != NULL) {
261        return GGML_STATUS_SUCCESS;
262    }
263
264    const size_t original_size = ggml_nbytes(tensor);
265    const size_t padded_size   = ggml_backend_buft_get_alloc_size(buffer->buft, tensor);
266
267    // Clear padding bytes to avoid NaN values
268    // XXX: Martin - do we need this?
269    if (padded_size > original_size) {
270        const size_t padding_size = padded_size - original_size;
271
272        // Get device context to access memops kernel
273        ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
274        if (!dev_ctx) {
275            GGML_LOG_ERROR("ET: Failed to get device context for padding clear");
276            return GGML_STATUS_FAILED;
277        }
278
279        // Use device-side memset kernel for efficient padding clear
280        std::byte * padding_ptr = static_cast<std::byte *>(tensor->data) + original_size;
281        if (!ggml_et_memset(dev_ctx, padding_ptr, 0, padding_size)) {
282            GGML_LOG_ERROR("ET: Failed to clear padding using memset kernel for tensor %s", tensor->name);
283            return GGML_STATUS_FAILED;
284        }
285    }
286
287    return GGML_STATUS_SUCCESS;
288}
289
290static void ggml_backend_et_buffer_set_tensor(ggml_backend_buffer_t buffer,
291                                              ggml_tensor *         tensor,
292                                              const void *          data,
293                                              size_t                offset,
294                                              size_t                size) {
295    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
296    if (!runtime) {
297        return;
298    }
299
300    // Create short-lived stream for this transfer
301    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
302    rt::StreamId                     stream  = dev_ctx->default_stream;
303
304    std::byte *       dst_ptr = static_cast<std::byte *>(tensor->data) + offset;
305    const std::byte * src_ptr = static_cast<const std::byte *>(data);
306
307    rt::EventId event = runtime->memcpyHostToDevice(stream, src_ptr, dst_ptr, size, true /*barrier*/);
308
309    runtime->waitForEvent(event);
310}
311
312static void ggml_backend_et_buffer_get_tensor(ggml_backend_buffer_t buffer,
313                                              const ggml_tensor *   tensor,
314                                              void *                data,
315                                              size_t                offset,
316                                              size_t                size) {
317    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
318    if (!runtime) {
319        return;
320    }
321
322    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
323    rt::StreamId                     stream  = dev_ctx->default_stream;
324
325    const std::byte * src_ptr = static_cast<const std::byte *>(tensor->data) + offset;
326    std::byte *       dst_ptr = static_cast<std::byte *>(data);
327
328    rt::EventId event = runtime->memcpyDeviceToHost(stream, src_ptr, dst_ptr, size, true /*barrier*/);
329
330    runtime->waitForEvent(event);
331}
332
333static bool ggml_backend_et_buffer_cpy_tensor(ggml_backend_buffer_t buffer,
334                                              const ggml_tensor *   src,
335                                              ggml_tensor *         dst) {
336    GGML_UNUSED(buffer);
337    GGML_UNUSED(src);
338    GGML_UNUSED(dst);
339    return false;
340}
341
342static void ggml_backend_et_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) {
343    ggml_backend_et_buffer_context * ctx = (ggml_backend_et_buffer_context *) buffer->context;
344
345    if (ctx->size == 0 || ctx->data == nullptr) {
346        return;
347    }
348
349    // Get device context to access memops kernel
350    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
351    if (!dev_ctx) {
352        GGML_LOG_ERROR("ET: Failed to get device context for buffer clear");
353        return;
354    }
355
356    // Use device-side memset kernel for efficient clearing
357    if (!ggml_et_memset(dev_ctx, ctx->data, value, ctx->size)) {
358        GGML_LOG_ERROR("ET: buffer_clear failed using memset kernel");
359        return;
360    }
361
362    GGML_LOG_DEBUG("ET: Buffer cleared successfully using memops kernel");
363}
364
365static const struct ggml_backend_buffer_i ggml_backend_et_buffer_i = {
366    /* .free_buffer     = */ ggml_backend_et_buffer_free_buffer,
367    /* .get_base        = */ ggml_backend_et_buffer_get_base,
368    /* .init_tensor     = */ ggml_backend_et_buffer_init_tensor,
369    /* .memset_tensor   = */ NULL,
370    /* .set_tensor      = */ ggml_backend_et_buffer_set_tensor,
371    /* .get_tensor      = */ ggml_backend_et_buffer_get_tensor,
372    /* .set_tensor_2d   = */ NULL,
373    /* .get_tensor_2d   = */ NULL,
374    /* .cpy_tensor      = */ ggml_backend_et_buffer_cpy_tensor,
375    /* .clear           = */ ggml_backend_et_buffer_clear,
376    /* .reset           = */ NULL,
377};
378
379static const char * ggml_backend_et_buffer_type_get_name(ggml_backend_buffer_type_t buft) {
380    GGML_UNUSED(buft);
381    return GGML_ET_NAME;
382}
383
384static ggml_backend_buffer_t ggml_backend_et_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
385    ggml_backend_et_buffer_type_context * btctx = (ggml_backend_et_buffer_type_context *) buft->context;
386
387    ggml_backend_et_buffer_context * ctx = new ggml_backend_et_buffer_context;
388    ctx->devidx                          = btctx->devidx;
389    ctx->size                            = size;
390
391    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
392    if (!runtime) {
393        delete ctx;
394        return nullptr;
395    }
396
397    std::vector<rt::DeviceId> rtids = runtime->getDevices();
398    if (static_cast<size_t>(btctx->devidx) >= rtids.size()) {
399        delete ctx;
400        return nullptr;
401    }
402    ctx->rtid = rtids[btctx->devidx];
403
404    ctx->data = runtime->mallocDevice(ctx->rtid, size);
405    if (ctx->data == nullptr) {
406        delete ctx;
407        return nullptr;
408    }
409
410    return ggml_backend_buffer_init(buft, ggml_backend_et_buffer_i, ctx, size);
411}
412
413static size_t ggml_backend_et_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) {
414    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
415    if (!runtime || !buft->device) {
416        return GGML_MEM_ALIGN;
417    }
418
419    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buft->device->context;
420    rt::DeviceProperties             prop    = runtime->getDeviceProperties(dev_ctx->rtid);
421    return prop.cacheLineSize_;
422}
423
424static size_t ggml_backend_et_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
425    if (buft->device) {
426        ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buft->device->context;
427        return dev_ctx->total_mem;
428    }
429    return SIZE_MAX;
430}
431
432static size_t ggml_backend_et_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
433    GGML_UNUSED(buft);
434    return ggml_nbytes_pad(tensor);
435}
436
437static bool ggml_backend_et_buffer_type_is_host(ggml_backend_buffer_type_t buft) {
438    GGML_UNUSED(buft);
439    return false;
440}
441
442static const struct ggml_backend_buffer_type_i ggml_backend_et_buffer_type_i = {
443    /* .get_name         = */ ggml_backend_et_buffer_type_get_name,
444    /* .alloc_buffer     = */ ggml_backend_et_buffer_type_alloc_buffer,
445    /* .get_alignment    = */ ggml_backend_et_buffer_type_get_alignment,
446    /* .get_max_size     = */ ggml_backend_et_buffer_type_get_max_size,
447    /* .get_alloc_size   = */ ggml_backend_et_buffer_type_get_alloc_size,
448    /* .is_host          = */ ggml_backend_et_buffer_type_is_host,
449};
450
451static const char * ggml_backend_et_get_name(ggml_backend_t backend) {
452    GGML_UNUSED(backend);
453    return GGML_ET_NAME;
454}
455
456static void ggml_backend_et_free(ggml_backend_t backend) {
457    ggml_backend_et_context *     et_ctx  = (ggml_backend_et_context *) backend->context;
458    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
459
460    // Clean up kernels on this device before freeing backend
461    ggml_backend_dev_t dev = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), et_ctx->devidx);
462    if (dev && dev->context) {
463        ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
464
465        if (_drv.profiling_enabled) {
466            auto kernels = ggml_et_get_loaded_kernels(dev_ctx);
467            _drv.kernel_map.insert(_drv.kernel_map.end(), kernels.begin(), kernels.end());
468        }
469
470        ggml_et_unload_all_kernels(dev_ctx);
471
472        if (runtime) {
473            if (dev_ctx->trace_buffer) {
474                runtime->freeDevice(dev_ctx->rtid, dev_ctx->trace_buffer);
475                dev_ctx->trace_buffer = nullptr;
476            }
477            // Drain any in-flight uberkernel launches before freeing the
478            // device buffers they read from.
479            runtime->waitForStream(dev_ctx->default_stream);
480            for (auto & slot : dev_ctx->uberkernel.slots) {
481                if (slot.device_insts) {
482                    runtime->freeDevice(dev_ctx->rtid, slot.device_insts);
483                    slot.device_insts = nullptr;
484                }
485                if (slot.device_params) {
486                    runtime->freeDevice(dev_ctx->rtid, slot.device_params);
487                    slot.device_params = nullptr;
488                }
489                slot.has_pending = false;
490            }
491        }
492    }
493
494    delete et_ctx;
495    delete backend;
496}
497
498static ggml_backend_buffer_type_t ggml_backend_et_get_default_buffer_type(ggml_backend_t backend) {
499    ggml_backend_et_context * et_ctx = (ggml_backend_et_context *) backend->context;
500
501    return ggml_backend_et_buffer_type(et_ctx->devidx);
502}
503
504static void ggml_backend_et_set_tensor_async(ggml_backend_t backend,
505                                             ggml_tensor *  tensor,
506                                             const void *   data,
507                                             size_t         offset,
508                                             size_t         size) {
509    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
510    if (!runtime) {
511        return;
512    }
513
514    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
515    rt::StreamId                     stream  = dev_ctx->default_stream;
516
517    std::byte *       dst_ptr = static_cast<std::byte *>(tensor->data) + offset;
518    const std::byte * src_ptr = static_cast<const std::byte *>(data);
519
520    runtime->memcpyHostToDevice(stream, src_ptr, dst_ptr, size, true /*barrier*/);
521}
522
523static void ggml_backend_et_get_tensor_async(ggml_backend_t      backend,
524                                             const ggml_tensor * tensor,
525                                             void *              data,
526                                             size_t              offset,
527                                             size_t              size) {
528    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
529    if (!runtime) {
530        return;
531    }
532
533    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
534    rt::StreamId                     stream  = dev_ctx->default_stream;
535
536    const std::byte * src_ptr = static_cast<const std::byte *>(tensor->data) + offset;
537    std::byte *       dst_ptr = static_cast<std::byte *>(data);
538
539    runtime->memcpyDeviceToHost(stream, src_ptr, dst_ptr, size, true /*barrier*/);
540}
541
542static bool ggml_backend_et_cpy_tensor_async(ggml_backend_t      backend_src,
543                                             ggml_backend_t      backend_dst,
544                                             const ggml_tensor * src,
545                                             ggml_tensor *       dst) {
546    GGML_UNUSED(backend_src);
547    GGML_UNUSED(backend_dst);
548    GGML_UNUSED(src);
549    GGML_UNUSED(dst);
550    return false;
551}
552
553static void ggml_backend_et_synchronize(ggml_backend_t backend) {
554    std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
555    if (!runtime) {
556        return;
557    }
558
559    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
560    runtime->waitForStream(dev_ctx->default_stream);
561
562    auto errors = runtime->retrieveStreamErrors(dev_ctx->default_stream);
563    if (errors.empty()) {
564        return;
565    }
566    for (const auto & err : errors) {
567        GGML_LOG_ERROR("ET: stream error detected at synchronization point. Code: %d,Type: %d\n", (int) err.errorCode_,
568                       (int) err.errorContext_.value()[0].type_);
569    }
570    abort();
571}
572
573static bool ggml_et_can_fuse(const ggml_cgraph * cgraph, int node_idx, std::initializer_list<ggml_op> ops) {
574    if (!ggml_can_fuse(cgraph, node_idx, ops)) {
575        return false;
576    }
577
578    if (ops.size() == 2 && ops.begin()[0] == GGML_OP_MUL_MAT && ops.begin()[1] == GGML_OP_ADD) {
579        const ggml_tensor * mm  = cgraph->nodes[node_idx];
580        const ggml_tensor * add = cgraph->nodes[node_idx + 1];
581
582        // Only Q8_0 weights x F32 activations -> F32 (the kernel that has
583        // the bias path).  Other MM variants must wait for their own kernel
584        // bias support.
585        if (mm->type != GGML_TYPE_F32 || mm->src[0]->type != GGML_TYPE_Q8_0 || mm->src[1]->type != GGML_TYPE_F32) {
586            return false;
587        }
588
589        // ADD must be F32 and one of its operands must be the MM output.
590        if (add->type != GGML_TYPE_F32) {
591            return false;
592        }
593        if (add->src[0] != mm && add->src[1] != mm) {
594            return false;
595        }
596
597        const ggml_tensor * bias = (add->src[0] == mm) ? add->src[1] : add->src[0];
598
599        if (bias->type != GGML_TYPE_F32) {
600            return false;
601        }
602
603        // No broadcasting: bias shape must equal MM output shape.
604        for (int i = 0; i < GGML_MAX_DIMS; ++i) {
605            if (bias->ne[i] != mm->ne[i]) {
606                return false;
607            }
608        }
609
610        // Bias and dst must be contiguous and have identical strides - the
611        // kernel uses dst-style offset arithmetic against bias's nb[].
612        if (!ggml_is_contiguous(bias) || !ggml_is_contiguous(mm)) {
613            return false;
614        }
615        for (int i = 0; i < GGML_MAX_DIMS; ++i) {
616            if ((int64_t) bias->nb[i] != (int64_t) add->nb[i]) {
617                return false;
618            }
619        }
620    }
621
622    if (ops.size() == 2 && ops.begin()[0] == GGML_OP_RMS_NORM && ops.begin()[1] == GGML_OP_MUL) {
623        const ggml_tensor * rms_norm = cgraph->nodes[node_idx];
624        const ggml_tensor * mul      = cgraph->nodes[node_idx + 1];
625
626        // ET only supports F32
627        if (rms_norm->src[0]->type != GGML_TYPE_F32 || mul->type != GGML_TYPE_F32) {
628            return false;
629        }
630
631        // Identify the weights tensor (the MUL operand that isn't rms_norm output)
632        const ggml_tensor * weights = (mul->src[0] == rms_norm) ? mul->src[1] : mul->src[0];
633
634        if (weights->type != GGML_TYPE_F32) {
635            return false;
636        }
637
638        // Both inputs must be contiguous (ET hardware requirement)
639        if (!ggml_is_contiguous(rms_norm->src[0]) || !ggml_is_contiguous_rows(weights)) {
640            return false;
641        }
642
643        // ET requires cache-aligned rows (ne[0] % 16 == 0)
644        if (rms_norm->src[0]->ne[0] % 16 != 0 || weights->ne[0] % 16 != 0) {
645            return false;
646        }
647
648        // Fused kernel doesn't handle dim-0 broadcasting
649        if (weights->ne[0] != rms_norm->src[0]->ne[0]) {
650            return false;
651        }
652    }
653
654    return true;
655}
656
657static ggml_status ggml_backend_et_graph_compute(ggml_backend_t backend, ggml_cgraph * cgraph) {
658    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
659    ggml_et_uberkernel_begin_graph(&dev_ctx->uberkernel);
660
661    for (int i = 0; i < cgraph->n_nodes; i++) {
662        ggml_tensor * node = cgraph->nodes[i];
663
664        if (node->op == GGML_OP_NONE || node->op == GGML_OP_VIEW || node->op == GGML_OP_RESHAPE ||
665            node->op == GGML_OP_PERMUTE || node->op == GGML_OP_TRANSPOSE) {
666            continue;
667        }
668
669        // --- Fusion checks (before regular dispatch) ---
670        if (ggml_et_can_fuse(cgraph, i, { GGML_OP_RMS_NORM, GGML_OP_MUL })) {
671            ggml_et_op_rms_norm_mul(dev_ctx, node, cgraph->nodes[i + 1]);
672            i++;  // skip the MUL node
673            continue;
674        }
675        if (ggml_et_can_fuse(cgraph, i, { GGML_OP_MUL_MAT, GGML_OP_ADD })) {
676            ggml_et_op_mul_mat(dev_ctx, node, cgraph->nodes[i + 1]);
677            i++;  // skip the ADD node
678            continue;
679        }
680
681        switch (node->op) {
682            case GGML_OP_SQR:
683                ggml_et_op_sqr(dev_ctx, node);
684                break;
685
686            case GGML_OP_UNARY:
687                ggml_et_op_unary(dev_ctx, node);
688                break;
689
690            case GGML_OP_SUM_ROWS:
691                ggml_et_op_sum_rows(dev_ctx, node);
692                break;
693
694            case GGML_OP_MEAN:
695                ggml_et_op_mean(dev_ctx, node);
696                break;
697
698            case GGML_OP_CLAMP:
699                ggml_et_op_clamp(dev_ctx, node);
700                break;
701
702            case GGML_OP_MUL:
703                ggml_et_op_mul(dev_ctx, node);
704                break;
705
706            case GGML_OP_ADD:
707                ggml_et_op_add(dev_ctx, node);
708                break;
709
710            case GGML_OP_SUB:
711                ggml_et_op_sub(dev_ctx, node);
712                break;
713
714            case GGML_OP_CUMSUM:
715                ggml_et_op_cumsum(dev_ctx, node);
716                break;
717
718            case GGML_OP_MUL_MAT:
719                ggml_et_op_mul_mat(dev_ctx, node);
720                break;
721
722            case GGML_OP_MUL_MAT_ID:
723                ggml_et_op_mul_mat_id(dev_ctx, node);
724                break;
725
726            case GGML_OP_ROPE:
727                ggml_et_op_rope(dev_ctx, node);
728                break;
729
730            case GGML_OP_RMS_NORM:
731                ggml_et_op_rms_norm(dev_ctx, node);
732                break;
733
734            case GGML_OP_NORM:
735                ggml_et_op_norm(dev_ctx, node);
736                break;
737
738            case GGML_OP_L2_NORM:
739                ggml_et_op_l2_norm(dev_ctx, node);
740                break;
741
742            case GGML_OP_GROUP_NORM:
743                ggml_et_op_group_norm(dev_ctx, node);
744                break;
745
746            case GGML_OP_SCALE:
747                ggml_et_op_scale(dev_ctx, node);
748                break;
749
750            case GGML_OP_GLU:
751                ggml_et_op_glu(dev_ctx, node);
752                break;
753
754            case GGML_OP_SOFT_MAX:
755                ggml_et_op_softmax(dev_ctx, node);
756                break;
757
758            case GGML_OP_IM2COL:
759                ggml_et_op_im2col(dev_ctx, node);
760                break;
761
762            case GGML_OP_CONV_2D:
763                ggml_et_op_conv_2d(dev_ctx, node);
764                break;
765
766            case GGML_OP_FLASH_ATTN_EXT:
767                ggml_et_op_flash_attn_ext(dev_ctx, node);
768                break;
769
770            case GGML_OP_GET_ROWS:
771                ggml_et_op_get_rows(dev_ctx, node);
772                break;
773
774            case GGML_OP_CONT:
775                ggml_et_op_cont(dev_ctx, node);
776                break;
777
778            case GGML_OP_CPY:
779                ggml_et_op_cpy(dev_ctx, node);
780                break;
781
782            case GGML_OP_CONCAT:
783                ggml_et_op_concat(dev_ctx, node);
784                break;
785
786            case GGML_OP_REPEAT:
787                ggml_et_op_repeat(dev_ctx, node);
788                break;
789
790            case GGML_OP_SSM_CONV:
791                ggml_et_op_ssm_conv(dev_ctx, node);
792                break;
793
794            case GGML_OP_SSM_SCAN:
795                ggml_et_op_ssm_scan(dev_ctx, node);
796                break;
797
798            case GGML_OP_PAD:
799                ggml_et_op_pad(dev_ctx, node);
800                break;
801
802            case GGML_OP_SET_ROWS:
803                ggml_et_op_set_rows(dev_ctx, node);
804                break;
805
806            case GGML_OP_FILL:
807                ggml_et_op_fill(dev_ctx, node);
808                break;
809
810            case GGML_OP_DIAG:
811                ggml_et_op_diag(dev_ctx, node);
812                break;
813
814            case GGML_OP_TRI:
815                ggml_et_op_tri(dev_ctx, node);
816                break;
817
818            case GGML_OP_SOLVE_TRI:
819                ggml_et_op_solve_tri(dev_ctx, node);
820                break;
821
822            case GGML_OP_SET:
823                ggml_et_op_set(dev_ctx, node);
824                break;
825
826            case GGML_OP_RWKV_WKV6:
827                ggml_et_op_rwkv_wkv6(dev_ctx, node);
828                break;
829
830            case GGML_OP_RWKV_WKV7:
831                ggml_et_op_rwkv_wkv7(dev_ctx, node);
832                break;
833
834            case GGML_OP_GATED_DELTA_NET:
835                ggml_et_op_gated_delta_net(dev_ctx, node);
836                break;
837
838            default:
839                ggml_et_uberkernel_abort_graph(&dev_ctx->uberkernel);
840                GGML_LOG_ERROR("ET: Unsupported operation in graph: %s", ggml_op_name(node->op));
841                return GGML_STATUS_FAILED;
842        }
843
844        if (ggml_et_uberkernel_failed(&dev_ctx->uberkernel)) {
845            ggml_et_uberkernel_abort_graph(&dev_ctx->uberkernel);
846            return GGML_STATUS_FAILED;
847        }
848    }
849
850    if (!ggml_et_uberkernel_end_graph(dev_ctx)) {
851        ggml_et_uberkernel_abort_graph(&dev_ctx->uberkernel);
852        return GGML_STATUS_FAILED;
853    }
854
855    return GGML_STATUS_SUCCESS;
856}
857
858// Check that elements within each row are contiguous (nb[0] == type_size).
859// Higher-dim strides can be arbitrary - kernels navigate them via byte offsets.
860static bool et_ggml_is_row_contiguous(const ggml_tensor * t) {
861    return t->nb[0] == ggml_type_size(t->type);
862}
863
864static bool ggml_backend_et_device_supports_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
865    GGML_UNUSED(dev);
866
867    bool supported = false;
868    switch (op->op) {
869        case GGML_OP_CUMSUM:
870            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
871                        op->src[0]->nb[0] == sizeof(float) && ggml_is_contiguous(op);
872            break;
873        case GGML_OP_SQR:
874            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
875                        op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
876            break;
877        case GGML_OP_SUM_ROWS:
878            // dst has ne[0]=1, src0 row length must be cache-aligned
879            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
880                        op->src[0]->ne[0] % 16 == 0 && ggml_is_contiguous(op->src[0]);
881            break;
882        case GGML_OP_MEAN:
883            // Kernel handles arbitrary ne00 (per-row alignment guard with
884            // scalar tail), so no row-length divisibility constraint here.
885            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
886                        ggml_is_contiguous(op->src[0]);
887            break;
888        case GGML_OP_CLAMP:
889            // Element-wise; kernel distributes by cache lines and handles a
890            // scalar tail, so any contiguous F32 size is fine - including the
891            // 1x1x1x1 scalar case.
892            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
893                        ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
894            break;
895        case GGML_OP_UNARY:
896            // Only require dim-0 contiguity (nb[0] == sizeof(float)). Higher
897            // dims may be arbitrarily strided views; the kernel walks per-row
898            // using all four nb[] values. See unary_f32.c entry_point.
899            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
900                ggml_nelements(op) % 16 == 0 && op->nb[0] == sizeof(float) && op->src[0]->nb[0] == sizeof(float)) {
901                switch (ggml_get_unary_op(op)) {
902                    case GGML_UNARY_OP_ABS:
903                    case GGML_UNARY_OP_SGN:
904                    case GGML_UNARY_OP_NEG:
905                    case GGML_UNARY_OP_STEP:
906                    case GGML_UNARY_OP_TANH:
907                    case GGML_UNARY_OP_ELU:
908                    case GGML_UNARY_OP_RELU:
909                    case GGML_UNARY_OP_SIGMOID:
910                    case GGML_UNARY_OP_GELU:
911                    case GGML_UNARY_OP_GELU_QUICK:
912                    case GGML_UNARY_OP_SILU:
913                    case GGML_UNARY_OP_HARDSWISH:
914                    case GGML_UNARY_OP_HARDSIGMOID:
915                    case GGML_UNARY_OP_EXP:
916                    case GGML_UNARY_OP_EXPM1:
917                    case GGML_UNARY_OP_SOFTPLUS:
918                    case GGML_UNARY_OP_GELU_ERF:
919                    case GGML_UNARY_OP_FLOOR:
920                    case GGML_UNARY_OP_CEIL:
921                    case GGML_UNARY_OP_ROUND:
922                    case GGML_UNARY_OP_TRUNC:
923                        supported = true;
924                        break;
925                    default:
926                        break;
927                }
928            }
929            break;
930        case GGML_OP_MUL:
931        case GGML_OP_ADD:
932        case GGML_OP_SUB:
933            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
934                        op->src[1]->type == GGML_TYPE_F32 && op->nb[0] == sizeof(float) &&
935                        op->src[0]->nb[0] == sizeof(float) &&
936                        (op->src[1]->nb[0] == sizeof(float) || op->src[1]->ne[0] == 1) &&
937                        op->nb[1] == op->ne[0] * sizeof(float);
938            break;
939        case GGML_OP_MUL_MAT:
940            // Support Q8_0 x F32 -> F32, F16 x F32 -> F32, F16 x F16 -> F32, and F32 x F32 -> F32 matrix multiplication
941            // Stride requirements: first dimension must be contiguous for all tensors
942            if (op->type == GGML_TYPE_F32 &&
943                ((op->src[0]->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32) ||
944                 (op->src[0]->type == GGML_TYPE_F16 && op->src[1]->type == GGML_TYPE_F16)) &&
945                op->ne[0] % 16 == 0 &&          // dst row length for tensor-store path
946                op->src[0]->ne[1] % 16 == 0 &&  // m
947                op->src[0]->ne[0] % 16 == 0 &&  // k
948                ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
949                // Special path for the FP32 TensorFMA kernel
950                // Limitation - generic kernels can tolerate non-cache-aligned dst rows
951                // because they publish each output element atomically. The matrix
952                // engine path still uses tiled tensor stores, so keep dst rows aligned.
953                // The m edge is difficult to do because of the 4 conseqtive load hardware limitation
954                // And the k edge is impossible because that is encoded as `stride & 0xFFFFFFFFFFC0ULL` which becomes 0 for stride 16 (4x FP32) :(
955                // FIXME: Right now this overwrites the mul_mat_f32 kernel - whatever. Fix later. Demo code
956                supported = true;
957            } else if (op->type == GGML_TYPE_F32 && op->src[0] &&
958                       (op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32) && op->src[1] &&
959                       (op->src[1]->type == GGML_TYPE_F16 || op->src[1]->type == GGML_TYPE_F32)) {
960                // Check first dimension contiguity requirements
961                bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
962                bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
963                bool dst_first_dim_contiguous  = (op->nb[0] == sizeof(float));
964
965                // Check destination stride ordering (only for dimensions with ne > 1)
966                bool dst_properly_ordered = true;
967                for (int d = 0; d < 3; d++) {
968                    if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
969                        dst_properly_ordered = false;
970                    }
971                }
972
973                supported = src0_first_dim_contiguous && src1_first_dim_contiguous && dst_first_dim_contiguous &&
974                            dst_properly_ordered;
975            } else if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_Q8_0 && op->src[1] &&
976                       op->src[1]->type == GGML_TYPE_F32) {
977                // Keep the existing quantized path constraints separate from the
978                // relaxed non-quant generic fallback.
979                bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
980                bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
981                bool dst_first_dim_contiguous  = (op->nb[0] == sizeof(float));
982
983                bool dst_properly_ordered = true;
984                for (int d = 0; d < 3; d++) {
985                    if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
986                        dst_properly_ordered = false;
987                    }
988                }
989
990                supported = src0_first_dim_contiguous && src1_first_dim_contiguous && dst_first_dim_contiguous &&
991                            dst_properly_ordered;
992
993            } else if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_Q4_0 && op->src[1] &&
994                       op->src[1]->type == GGML_TYPE_F32) {
995                // Keep the existing quantized path constraints separate from the
996                // relaxed non-quant generic fallback.
997                bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
998                bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
999                bool dst_first_dim_contiguous  = (op->nb[0] == sizeof(float));
1000
1001                bool dst_properly_ordered = true;
1002                for (int d = 0; d < 3; d++) {
1003                    if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
1004                        dst_properly_ordered = false;
1005                    }
1006                }
1007
1008                supported = src0_first_dim_contiguous && src1_first_dim_contiguous && dst_first_dim_contiguous &&
1009                            dst_properly_ordered;
1010            } else {
1011                supported = false;
1012            }
1013            break;
1014        case GGML_OP_MUL_MAT_ID:
1015            // Support MUL_MAT_ID for Mixture of Experts: (Q8_0/Q4_0/F16/F32) x F32 -> F32 with I32 expert indices
1016            // src0 (as): [K, M, n_expert] - expert weight matrices (can be quantized)
1017            // src1 (b):  [K, n_expert_used, batch] - activations (F32)
1018            // src2 (ids): [n_expert_used, batch] - expert selection indices (I32)
1019            // dst: [M, n_expert_used, batch, 1] - output (F32)
1020            if (op->type == GGML_TYPE_F32 && op->src[0] &&
1021                (op->src[0]->type == GGML_TYPE_Q8_0 || op->src[0]->type == GGML_TYPE_Q4_0 ||
1022                 op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32) &&
1023                op->src[1] && op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_I32) {
1024                // Check first dimension contiguity requirements (matching CPU backend)
1025                bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
1026                bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
1027                bool src2_first_dim_contiguous = (op->src[2]->nb[0] == ggml_type_size(op->src[2]->type));
1028                bool dst_first_dim_contiguous  = (op->nb[0] == sizeof(float));
1029
1030                // Check destination stride ordering (only for dimensions with ne > 1)
1031                bool dst_properly_ordered = true;
1032                for (int d = 0; d < 3; d++) {
1033                    if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
1034                        dst_properly_ordered = false;
1035                    }
1036                }
1037
1038                // Validate tensor dimension constraints from GGML definition
1039                bool dims_valid = (op->src[0]->ne[3] == 1) &&  // as is 3d (one matrix per expert)
1040                                  (op->src[1]->ne[3] == 1) &&  // b is 3d
1041                                  (op->src[2]->ne[2] == 1 && op->src[2]->ne[3] == 1) &&  // ids is 2d
1042                                  (op->src[2]->ne[1] == op->src[1]->ne[2]) &&    // must have expert list per b row
1043                                  (op->src[0]->ne[0] == op->src[1]->ne[0]) &&    // K dimension must match
1044                                  (op->src[2]->ne[0] % op->src[1]->ne[1] == 0);  // can broadcast
1045
1046                supported = src0_first_dim_contiguous && src1_first_dim_contiguous && src2_first_dim_contiguous &&
1047                            dst_first_dim_contiguous && dst_properly_ordered && dims_valid;
1048            } else {
1049                supported = false;
1050            }
1051            break;
1052        case GGML_OP_ROPE:
1053            // Support F32 x I32 -> F32 RoPE for the modes implemented by rope_f32.
1054            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1055                op->src[1]->type == GGML_TYPE_I32 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0])) {
1056                const int  mode             = ggml_get_op_params_i32(op, 2);
1057                const int  ndims            = ggml_get_op_params_i32(op, 1);
1058                const bool is_normal        = mode == GGML_ROPE_TYPE_NORMAL;
1059                const bool is_neox          = mode == GGML_ROPE_TYPE_NEOX;
1060                const bool is_imrope        = mode == GGML_ROPE_TYPE_IMROPE;
1061                const bool zero_view_offset = op->src[0]->view_src == nullptr || op->src[0]->view_offs == 0;
1062                const bool has_sections = ggml_get_op_params_i32(op, 11) > 0 || ggml_get_op_params_i32(op, 12) > 0 ||
1063                                          ggml_get_op_params_i32(op, 13) > 0;
1064                // FIXME: support ggml_rope_set_offset
1065                const bool zero_rot_offset  = ggml_get_op_params_i32(op, 15) == 0;
1066
1067                supported =
1068                    zero_view_offset && zero_rot_offset && ndims <= 512 &&
1069                    (is_normal || (is_neox && ndims % 16 == 0) || (is_imrope && ndims % 16 == 0 && has_sections));
1070            } else {
1071                supported = false;
1072            }
1073            break;
1074        case GGML_OP_RMS_NORM:
1075            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1076                        op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]);
1077            break;
1078        case GGML_OP_NORM:
1079            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1080                        op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]);
1081            break;
1082        case GGML_OP_L2_NORM:
1083            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1084                        op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]);
1085            break;
1086        case GGML_OP_GROUP_NORM:
1087            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1088                        ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]) &&
1089                        ggml_get_op_params_i32(op, 0) > 0;
1090            break;
1091        case GGML_OP_IM2COL:
1092            supported = op->src[0] && op->src[1] &&
1093                        ((op->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32) ||
1094                         (op->type == GGML_TYPE_F16 &&
1095                          (op->src[1]->type == GGML_TYPE_F16 || op->src[1]->type == GGML_TYPE_F32))) &&
1096                        ggml_is_contiguous(op) && ggml_is_contiguous(op->src[1]) &&
1097                        op->nb[0] == ggml_type_size(op->type) && op->src[1]->nb[0] == ggml_type_size(op->src[1]->type);
1098            break;
1099        case GGML_OP_CONV_2D:
1100            {
1101                // First-cut conv_2d_f32_me kernel constraints. Anything outside
1102                // this falls back to CPU (it's a strict subset on purpose).
1103                if (!op->src[0] || !op->src[1]) {
1104                    supported = false;
1105                    break;
1106                }
1107                if (op->type != GGML_TYPE_F32 || op->src[0]->type != GGML_TYPE_F32 ||
1108                    op->src[1]->type != GGML_TYPE_F32) {
1109                    supported = false;
1110                    break;
1111                }
1112                if (!ggml_is_contiguous(op) || !ggml_is_contiguous(op->src[0]) || !ggml_is_contiguous(op->src[1])) {
1113                    supported = false;
1114                    break;
1115                }
1116
1117                const ggml_tensor * flt = op->src[0];  // [Kw, Kh, Cin, Cout]
1118                const ggml_tensor * in  = op->src[1];  // [W,  H,  Cin, N]
1119                const int32_t       s0  = ggml_get_op_params_i32(op, 0);
1120                const int32_t       s1  = ggml_get_op_params_i32(op, 1);
1121                const int32_t       p0  = ggml_get_op_params_i32(op, 2);
1122                const int32_t       p1  = ggml_get_op_params_i32(op, 3);
1123                const int32_t       d0  = ggml_get_op_params_i32(op, 4);
1124                const int32_t       d1  = ggml_get_op_params_i32(op, 5);
1125
1126                const int64_t Kw   = flt->ne[0];
1127                const int64_t Kh   = flt->ne[1];
1128                const int64_t Cin  = flt->ne[2];
1129                const int64_t Cout = flt->ne[3];
1130                const int64_t H    = in->ne[1];
1131                (void) in->ne[0];
1132
1133                if (s0 < 1 || s1 < 1 || !(d0 == 1 && d1 == 1) || Cin % 16 != 0 || Cout % 16 != 0 || in->ne[3] != 1) {
1134                    supported = false;
1135                    break;
1136                }
1137                const int64_t OW = op->ne[0];
1138                const int64_t OH = op->ne[1];
1139                if (OW <= 0 || OH <= 0) {
1140                    supported = false;
1141                    break;
1142                }
1143                (void) p0;
1144                (void) p1;
1145
1146                // Mirror the kernel's sizing:
1147                //   if K_TILES * per_KT_bytes <= budget: 1 buffer, n_chunks=1
1148                //   else: 2 buffers (double-buffer), shrink chunk_KT until
1149                //         2*chunk_KT*per_KT_bytes <= budget.
1150                const int64_t Hp            = H + 2 * p1;
1151                const int64_t OW_pad        = (OW + 15) & ~15;
1152                const int64_t Wp_a          = OW_pad;
1153                const bool    need_stage    = (OW % 16 != 0);
1154                const int64_t stage_bytes   = need_stage ? (Cout * OH * OW_pad * 4) : 0;
1155                const int64_t L2SCP_BUDGET  = 1500 * 1024;
1156                // Per-hart partial-TenC scratch (mirrors kernel MAX_TILES_PER_HART=2):
1157                // 32 minions x 2 tiles x 1024 bytes = 64 KB per shire.
1158                const int64_t scratch_bytes = 32 * 2 * 16 * 16 * 4;
1159                const int64_t budget        = L2SCP_BUDGET - stage_bytes - scratch_bytes;
1160                const int64_t per_KT_bytes  = Kh * Kw * Cout * 16 * 4 + Kw * 16 * Hp * Wp_a * 4;
1161                const int64_t K_TILES       = Cin / 16;
1162
1163                int64_t chunk_KT_calc;
1164                int64_t n_chunks_calc;
1165                if (K_TILES * per_KT_bytes <= budget) {
1166                    chunk_KT_calc = K_TILES;
1167                    n_chunks_calc = 1;
1168                } else {
1169                    chunk_KT_calc = K_TILES;
1170                    while (chunk_KT_calc > 1 && 2 * chunk_KT_calc * per_KT_bytes > budget) {
1171                        chunk_KT_calc--;
1172                    }
1173                    while (chunk_KT_calc > 1 && K_TILES % chunk_KT_calc != 0) {
1174                        chunk_KT_calc--;
1175                    }
1176                    if (chunk_KT_calc < 1) {
1177                        supported = false;
1178                        break;
1179                    }
1180                    n_chunks_calc = K_TILES / chunk_KT_calc;
1181                }
1182
1183                if (n_chunks_calc > 1) {
1184                    const int64_t M_TILES     = Cout / 16;
1185                    const int64_t w_tiles     = (OW + 15) / 16;
1186                    const int64_t total_tiles = OH * w_tiles * M_TILES;
1187                    // MAX_TILES_PER_HART = 2 (mirrors kernel constant).
1188                    const int64_t max_workers = (need_stage ? 32 : 1024) * 2;
1189                    if (total_tiles > max_workers) {
1190                        supported = false;
1191                        break;
1192                    }
1193                }
1194
1195                supported = true;
1196                break;
1197            }
1198        case GGML_OP_SCALE:
1199            // F32 contiguous, total elements must be cache line aligned (16 floats)
1200            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1201                        ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && (ggml_nelements(op) % 16 == 0);
1202            break;
1203        case GGML_OP_GLU:
1204            // Note: we only require row-wise contiguity (ggml_is_contiguous_1) so that
1205            // strided views over a packed up_proj tensor (the common split-GLU layout)
1206            // are accepted. The kernel walks rows via nb[1] strides, so the inner
1207            // dimension just needs to be densely packed.
1208            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1209                ggml_nelements(op) % 16 == 0 && ggml_is_contiguous_1(op) && ggml_is_contiguous_1(op->src[0])) {
1210                // Check GLU variant - support SWIGLU, SWIGLU_OAI, GEGLU, GEGLU_ERF, GEGLU_QUICK, REGLU
1211                ggml_glu_op glu_type          = ggml_get_glu_op(op);
1212                const bool  supported_variant = glu_type == GGML_GLU_OP_SWIGLU || glu_type == GGML_GLU_OP_SWIGLU_OAI ||
1213                                                glu_type == GGML_GLU_OP_SWIGLU_CLAMP || glu_type == GGML_GLU_OP_GEGLU ||
1214                                                glu_type == GGML_GLU_OP_GEGLU_ERF ||
1215                                                glu_type == GGML_GLU_OP_GEGLU_QUICK || glu_type == GGML_GLU_OP_REGLU;
1216
1217                if (op->src[1]) {
1218                    supported = supported_variant && op->src[1]->type == GGML_TYPE_F32 &&
1219                                ggml_is_contiguous_1(op->src[1]) && op->src[0]->ne[0] == op->ne[0] &&
1220                                op->src[1]->ne[0] == op->ne[0];
1221                } else {
1222                    supported = supported_variant && op->src[0]->ne[0] == 2 * op->ne[0];
1223                }
1224            } else {
1225                supported = false;
1226            }
1227            break;
1228        case GGML_OP_SOFT_MAX:
1229            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1230                ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && op->src[0]->ne[0] > 1) {
1231                // Check optional mask tensor (F32 only)
1232                if (op->src[1]) {
1233                    supported = op->src[1]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[1]);
1234                    if (!supported) {
1235                        break;
1236                    }
1237                }
1238                // Check optional sinks tensor (F32 only)
1239                if (op->src[2]) {
1240                    supported = op->src[2]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[2]);
1241                } else {
1242                    supported = true;
1243                }
1244            } else {
1245                supported = false;
1246            }
1247            break;
1248        case GGML_OP_SSM_SCAN:
1249            supported = op->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && op->src[0] &&
1250                        op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[0]) && op->src[1] &&
1251                        op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_F32 &&
1252                        ggml_is_contiguous(op->src[2]) && op->src[3] && op->src[3]->type == GGML_TYPE_F32 &&
1253                        ggml_is_contiguous(op->src[3]) && op->src[4] && op->src[4]->type == GGML_TYPE_F32 &&
1254                        op->src[5] && op->src[5]->type == GGML_TYPE_F32 && op->src[6] &&
1255                        op->src[6]->type == GGML_TYPE_I32 && ggml_is_contiguous(op->src[6]) &&
1256                        op->src[1]->nb[0] == sizeof(float) && op->src[4]->nb[0] == sizeof(float) &&
1257                        op->src[5]->nb[0] == sizeof(float) &&
1258                        op->src[1]->nb[1] == (size_t) op->src[1]->ne[0] * sizeof(float) &&
1259                        op->src[4]->nb[1] == (size_t) op->src[4]->ne[0] * sizeof(float) &&
1260                        op->src[5]->nb[1] == (size_t) op->src[5]->ne[0] * sizeof(float) &&
1261                        op->src[0]->ne[0] == op->src[4]->ne[0] && op->src[0]->ne[1] == op->src[1]->ne[0] &&
1262                        op->src[0]->ne[2] == op->src[1]->ne[1] && op->src[1]->ne[2] == op->src[2]->ne[1] &&
1263                        op->src[1]->ne[3] == op->src[2]->ne[2] && op->src[4]->ne[2] == op->src[1]->ne[2] &&
1264                        op->src[4]->ne[3] == op->src[1]->ne[3] && ggml_are_same_shape(op->src[4], op->src[5]) &&
1265                        op->src[6]->ne[0] == op->src[1]->ne[3] && op->src[3]->ne[1] == op->src[1]->ne[1] &&
1266                        (op->src[3]->ne[0] == 1 || op->src[3]->ne[0] == op->src[0]->ne[0]) &&
1267                        (op->src[1]->ne[1] % op->src[4]->ne[1] == 0);
1268            break;
1269        case GGML_OP_FLASH_ATTN_EXT:
1270            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1271                (op->src[1]->type == GGML_TYPE_F32 || op->src[1]->type == GGML_TYPE_F16) && op->src[2] &&
1272                (op->src[2]->type == GGML_TYPE_F32 || op->src[2]->type == GGML_TYPE_F16) && op->src[4] == nullptr &&
1273                ggml_is_contiguous_rows(op) && ggml_is_contiguous_rows(op->src[0])) {
1274                float max_bias      = 0.0f;
1275                float logit_softcap = 0.0f;
1276                memcpy(&max_bias, (const float *) op->op_params + 1, sizeof(max_bias));
1277                memcpy(&logit_softcap, (const float *) op->op_params + 2, sizeof(logit_softcap));
1278
1279                const ggml_prec prec = ggml_flash_attn_ext_get_prec(op);
1280
1281                // Mask must be F16 or F32 if present
1282                bool mask_ok = (op->src[3] == nullptr) || (op->src[3]->type == GGML_TYPE_F32) ||
1283                               (op->src[3]->type == GGML_TYPE_F16);
1284
1285                // GQA: n_head_q must be a multiple of n_head_kv
1286                const int64_t nhq = op->src[0]->ne[2];
1287                const int64_t nhk = op->src[1]->ne[2];
1288
1289                // K/V row stride must match element size
1290                const size_t k_elem = op->src[1]->type == GGML_TYPE_F16 ? 2 : 4;
1291                const size_t v_elem = op->src[2]->type == GGML_TYPE_F16 ? 2 : 4;
1292
1293                // Only support matrix engine path (F16 K/V, dk%32==0);
1294                // mask scalar F32 fallback to get baseline perf readings
1295                const bool me_eligible = op->src[1]->type == GGML_TYPE_F16 && op->src[2]->type == GGML_TYPE_F16 &&
1296                                         (op->src[0]->ne[0] % 32) == 0;
1297
1298                supported = me_eligible && mask_ok && (prec == GGML_PREC_F32 || prec == GGML_PREC_DEFAULT) &&
1299                            max_bias == 0.0f && logit_softcap == 0.0f && op->src[0]->nb[0] == sizeof(float) &&
1300                            op->src[1]->nb[0] == k_elem && op->src[2]->nb[0] == v_elem && op->nb[0] == sizeof(float) &&
1301                            op->src[0]->ne[0] == op->src[1]->ne[0] &&  // dk matches
1302                            op->src[2]->ne[0] == op->ne[0] &&          // dv matches
1303                            op->src[2]->ne[0] <= 512 &&                // dv limit
1304                            op->src[0]->ne[0] <= 512 &&                // dk limit
1305                            nhq % nhk == 0 &&                          // GQA ratio is integer
1306                            op->src[0]->ne[1] == op->ne[2] && op->src[0]->ne[2] == op->ne[1] &&
1307                            op->src[0]->ne[3] == op->ne[3] && op->src[1]->ne[1] == op->src[2]->ne[1] &&
1308                            op->src[1]->ne[2] == op->src[2]->ne[2] && op->src[1]->ne[3] == op->src[2]->ne[3] &&
1309                            op->src[0]->ne[3] == op->src[1]->ne[3];
1310            } else {
1311                supported = false;
1312            }
1313            break;
1314        case GGML_OP_GET_ROWS:
1315            // Support F32/F16/Q4_0/Q8_0/Q4_K data with I32 indices -> F32 output
1316            if (op->type == GGML_TYPE_F32 && op->src[0] &&
1317                (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16 ||
1318                 op->src[0]->type == GGML_TYPE_Q4_0 || op->src[0]->type == GGML_TYPE_Q8_0 ||
1319                 op->src[0]->type == GGML_TYPE_Q4_K) &&
1320                op->src[1] && op->src[1]->type == GGML_TYPE_I32 && ggml_is_contiguous(op) &&
1321                ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
1322                // Validate dimension constraints from ggml implementation
1323                supported = (op->src[0]->ne[2] == op->src[1]->ne[1]) && (op->src[1]->ne[3] == 1);
1324            } else {
1325                supported = false;
1326            }
1327            break;
1328        case GGML_OP_CONT:
1329            // Support F32->F32 and F16->F16 CONT operations (rearrange non-contiguous to contiguous)
1330            if ((op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && op->src[0] &&
1331                op->src[0]->type == op->type && ggml_is_contiguous(op)) {
1332                // Defensive check: ensure dst and src0 are not aliased (separate buffers)
1333                // While GGML design currently guarantees this, check for future robustness
1334                if (op->data && op->src[0]->data && op->data == op->src[0]->data) {
1335                    GGML_LOG_WARN("ET: CONT operation detected aliased tensors (dst == src0), unsupported");
1336                    supported = false;
1337                } else {
1338                    supported = true;
1339                }
1340            } else {
1341                supported = false;
1342            }
1343            break;
1344        case GGML_OP_CPY:
1345            // CPY copies src[0] data into dst layout (same as CONT for same-type)
1346            // Special path: zero-element tensors (scalars) are accepted as no-ops
1347            if (op->src[0]) {
1348                const int64_t nelements = op->ne[0] * op->ne[1] * op->ne[2] * op->ne[3];
1349                if (nelements == 0) {
1350                    // Zero-element / scalar no-op case - always supported
1351                    supported = true;
1352                } else if ((op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && op->src[0]->type == op->type &&
1353                           ggml_is_contiguous(op)) {
1354                    // Same-type with contiguous dst - reuse CONT kernel
1355                    if (op->data && op->src[0]->data && op->data == op->src[0]->data) {
1356                        GGML_LOG_WARN("ET: CPY operation detected aliased tensors, unsupported");
1357                        supported = false;
1358                    } else {
1359                        supported = true;
1360                    }
1361                } else if (op->type == GGML_TYPE_F16 && op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguous(op)) {
1362                    // F32 -> F16 conversion copy
1363                    supported = true;
1364                } else {
1365                    supported = false;
1366                }
1367            } else {
1368                supported = false;
1369            }
1370            break;
1371        case GGML_OP_CONCAT:
1372            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1373                op->src[1]->type == GGML_TYPE_F32 && ggml_is_contiguous(op)) {
1374                const int32_t dim = ((const int32_t *) op->op_params)[0];
1375                if (dim == 0 && op->src[0]->ne[0] % 16 == 0 && op->src[1]->ne[0] % 16 == 0 &&
1376                    ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
1377                    // Fast dim==0 path: both source row segments are cacheline-aligned
1378                    // and contiguous, so the kernel can use vector row copies.
1379                    supported = true;
1380                } else if (dim == 0 && ((op->src[0]->nb[0] % sizeof(float) == 0) || op->src[0]->ne[0] == 1) &&
1381                           ((op->src[1]->nb[0] % sizeof(float) == 0) || op->src[1]->ne[0] == 1)) {
1382                    // Slow dim==0 path: scalar, stride-aware copies for non-contiguous
1383                    // or non-aligned source row segments. Destination remains contiguous.
1384                    supported = true;
1385                } else if (op->ne[0] % 16 == 0 && op->src[0]->ne[0] % 16 == 0 && op->src[1]->ne[0] % 16 == 0 &&
1386                           ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
1387                    // Dim >= 1 path: full aligned row copies from one source or the other.
1388                    supported = true;
1389                }
1390            }
1391            break;
1392        case GGML_OP_SSM_CONV:
1393            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1394                        op->src[1]->type == GGML_TYPE_F32 && op->src[0]->nb[0] == sizeof(float) &&
1395                        op->src[1]->nb[0] == sizeof(float) && op->src[0]->nb[1] == op->src[0]->ne[0] * sizeof(float) &&
1396                        op->src[1]->nb[1] == op->src[1]->ne[0] * sizeof(float) && ggml_is_contiguous(op) &&
1397                        op->src[1]->ne[1] == op->src[0]->ne[1] && op->ne[0] == op->src[0]->ne[1] &&
1398                        op->ne[1] == op->src[0]->ne[0] - op->src[1]->ne[0] + 1 && op->ne[2] == op->src[0]->ne[2];
1399            break;
1400        case GGML_OP_PAD:
1401            // F32 zero-pad only, no dim0 padding, dst contiguous
1402            // ne[0] must be CL-aligned (% 16 == 0) or evenly divide a CL (16 % ne[0] == 0)
1403            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1404                ggml_is_contiguous(op) && (op->ne[0] % 16 == 0 || 16 % op->ne[0] == 0) &&
1405                op->src[0]->nb[0] == sizeof(float)) {
1406                const int32_t lp0      = ((const int32_t *) op->op_params)[0];
1407                const int32_t rp0      = ((const int32_t *) op->op_params)[1];
1408                const bool    circular = (bool) ((const int32_t *) op->op_params)[8];
1409                if (lp0 == 0 && rp0 == 0 && !circular) {
1410                    supported = true;
1411                } else {
1412                    supported = false;
1413                }
1414            } else {
1415                supported = false;
1416            }
1417            break;
1418        case GGML_OP_REPEAT:
1419            // Two acceptable shapes:
1420            //   1. No-op REPEAT (src and dst have identical shape): dispatched
1421            //      to cont_f32, which handles arbitrary contiguous sizes.
1422            //   2. Real REPEAT via repeat_f32 kernel: dst ne[0] cacheline-aligned,
1423            //      src0 ne[0] cacheline-aligned or 1, dst.ne[i] % src0.ne[i] == 0.
1424            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1425                ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && ggml_are_same_shape(op->src[0], op)) {
1426                supported = true;
1427            } else if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1428                       (op->src[0]->ne[0] == 1 || op->src[0]->ne[0] % 16 == 0) && op->ne[0] % 16 == 0 &&
1429                       ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && op->ne[0] % op->src[0]->ne[0] == 0 &&
1430                       op->ne[1] % op->src[0]->ne[1] == 0 && op->ne[2] % op->src[0]->ne[2] == 0 &&
1431                       op->ne[3] % op->src[0]->ne[3] == 0) {
1432                supported = true;
1433            } else {
1434                supported = false;
1435            }
1436            break;
1437        case GGML_OP_FILL:
1438            // F32 contiguous, ne[0] cacheline-aligned for SIMD fill
1439            supported = op->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && op->ne[0] % 16 == 0;
1440            break;
1441        case GGML_OP_DIAG:
1442            // F32 contiguous dst, src0 is 1D vector [N,1,...], dst is [N,N,...]
1443            // ne[0] must be cacheline-aligned for SIMD zeroing
1444            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1445                        op->ne[0] % 16 == 0 && op->ne[0] == op->ne[1] && op->src[0]->ne[0] == op->ne[0] &&
1446                        op->src[0]->ne[1] == 1 && ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
1447            break;
1448        case GGML_OP_TRI:
1449            // F32 contiguous, same shape in/out
1450            // Kernel handles arbitrary ne[0] with aligned fast path + scalar fallback
1451            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
1452                        ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
1453            break;
1454        case GGML_OP_SOLVE_TRI:
1455            // F32 contiguous, A square, shapes compatible
1456            // Only lower-triangular left-side non-unit variant
1457            // Require k % 16 == 0 for cache-line-safe column parallelism
1458            supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1459                        op->src[1]->type == GGML_TYPE_F32 && op->src[0]->ne[0] == op->src[0]->ne[1] &&
1460                        op->src[0]->ne[1] == op->src[1]->ne[1] && op->src[1]->ne[0] % 16 == 0 &&
1461                        ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
1462            break;
1463        case GGML_OP_SET:
1464            // Minimal useful support: inplace F32 SET of a contiguous src1 view into
1465            // a contiguous dst/base tensor using explicit destination view strides.
1466            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1467                op->src[1]->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) &&
1468                ggml_is_contiguous(op->src[1]) && ggml_are_same_shape(op, op->src[0]) && op->src[1]->ne[0] % 16 == 0) {
1469                const bool   inplace = (bool) ((const int32_t *) op->op_params)[4];
1470                const size_t nb1     = ((const int32_t *) op->op_params)[0];
1471                const size_t nb2     = ((const int32_t *) op->op_params)[1];
1472                const size_t nb3     = ((const int32_t *) op->op_params)[2];
1473                const size_t offset  = ((const int32_t *) op->op_params)[3];
1474                const size_t nb0     = ggml_element_size(op);
1475                const size_t im0     = op->src[1]->ne[0] == 0 ? 0 : op->src[1]->ne[0] - 1;
1476                const size_t im1     = op->src[1]->ne[1] == 0 ? 0 : op->src[1]->ne[1] - 1;
1477                const size_t im2     = op->src[1]->ne[2] == 0 ? 0 : op->src[1]->ne[2] - 1;
1478                const size_t im3     = op->src[1]->ne[3] == 0 ? 0 : op->src[1]->ne[3] - 1;
1479
1480                const bool view_bounds_ok = offset + im0 * nb0 + im1 * nb1 + im2 * nb2 + im3 * nb3 <= ggml_nbytes(op);
1481
1482                const bool cacheline_aligned =
1483                    (nb1 % 64 == 0) && (nb2 % 64 == 0) && (nb3 % 64 == 0) && (offset % 64 == 0);
1484
1485                supported = inplace && view_bounds_ok && cacheline_aligned;
1486            }
1487            break;
1488        case GGML_OP_RWKV_WKV6:
1489            // F32 contiguous, head_size must be multiple of 8 for vectorization
1490            // 6 sources: k, v, r, tf, td, state
1491            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1492                op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_F32 && op->src[3] &&
1493                op->src[3]->type == GGML_TYPE_F32 && op->src[4] && op->src[4]->type == GGML_TYPE_F32 && op->src[5] &&
1494                op->src[5]->type == GGML_TYPE_F32 && op->src[0]->ne[0] % 8 == 0 &&  // head_size multiple of 8
1495                ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]) && ggml_is_contiguous(op->src[2]) &&
1496                ggml_is_contiguous(op->src[3]) && ggml_is_contiguous(op->src[4]) && ggml_is_contiguous(op->src[5])) {
1497                supported = true;
1498            } else {
1499                supported = false;
1500            }
1501            break;
1502        case GGML_OP_RWKV_WKV7:
1503            // F32 contiguous, head_size must be multiple of 8 for vectorization
1504            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
1505                op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_F32 && op->src[3] &&
1506                op->src[3]->type == GGML_TYPE_F32 && op->src[4] && op->src[4]->type == GGML_TYPE_F32 && op->src[5] &&
1507                op->src[5]->type == GGML_TYPE_F32 && op->src[6] && op->src[6]->type == GGML_TYPE_F32 &&
1508                op->src[2]->ne[0] % 8 == 0 &&  // head_size multiple of 8
1509                ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]) && ggml_is_contiguous(op->src[2]) &&
1510                ggml_is_contiguous(op->src[3]) && ggml_is_contiguous(op->src[4]) && ggml_is_contiguous(op->src[5]) &&
1511                ggml_is_contiguous(op->src[6])) {
1512                supported = true;
1513            } else {
1514                supported = false;
1515            }
1516            break;
1517        case GGML_OP_GATED_DELTA_NET:
1518            // F32, S_v must be multiple of 8 for vectorization
1519            // q, k, v may be row-contiguous with strided higher dimensions.
1520            // g, beta, state stay contiguous.
1521            if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&  // q
1522                op->src[1] && op->src[1]->type == GGML_TYPE_F32 &&                               // k
1523                op->src[2] && op->src[2]->type == GGML_TYPE_F32 &&                               // v
1524                op->src[3] && op->src[3]->type == GGML_TYPE_F32 &&                               // g
1525                op->src[4] && op->src[4]->type == GGML_TYPE_F32 &&                               // beta
1526                op->src[5] && op->src[5]->type == GGML_TYPE_F32 &&                               // state
1527                op->src[2]->ne[0] % 8 == 0 &&                                                    // S_v multiple of 8
1528                (op->src[3]->ne[0] == 1 || op->src[3]->ne[0] == op->src[2]->ne[0]) &&  // g is scalar or per-element
1529                op->src[4]->ne[0] == 1 &&                                              // beta is scalar per position
1530                et_ggml_is_row_contiguous(op->src[0]) && et_ggml_is_row_contiguous(op->src[1]) &&
1531                et_ggml_is_row_contiguous(op->src[2]) && ggml_is_contiguous(op->src[3]) &&
1532                ggml_is_contiguous(op->src[4]) && ggml_is_contiguous(op->src[5])) {
1533                supported = true;
1534            } else {
1535                supported = false;
1536            }
1537            break;
1538        case GGML_OP_VIEW:
1539        case GGML_OP_PERMUTE:
1540        case GGML_OP_TRANSPOSE:
1541        case GGML_OP_RESHAPE:
1542            // Metadata-only no-ops, accept any type
1543            supported = true;
1544            break;
1545        case GGML_OP_SET_ROWS:
1546            // Support F32 data with I64 indices -> F16/F32 output (scatter operation)
1547            if (op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] && op->src[1]->type == GGML_TYPE_I64 &&
1548                (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && ggml_is_contiguous_rows(op) &&
1549                ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous(op->src[1])) {
1550                // Validate dimension constraints from ggml implementation
1551                supported = (op->ne[0] == op->src[0]->ne[0]) &&              // same number of columns
1552                            (op->ne[2] == op->src[0]->ne[2]) &&              // same batch size
1553                            (op->ne[3] == op->src[0]->ne[3]) &&              // same outer dimension
1554                            (op->src[0]->ne[1] == op->src[1]->ne[0]) &&      // src rows = index count
1555                            (op->src[0]->ne[2] % op->src[1]->ne[1] == 0) &&  // batch constraint
1556                            (op->src[0]->ne[3] % op->src[1]->ne[2] == 0) &&  // outer constraint
1557                            (op->src[1]->ne[3] == 1);                        // indices tensor constraint
1558            } else {
1559                supported = false;
1560            }
1561            break;
1562        case GGML_OP_NONE:
1563            // Always support NONE operations - they represent leaf nodes (parameters, inputs, constants)
1564            // No computation needed, just memory management
1565            supported = true;
1566            break;
1567        default:
1568            supported = false;
1569            break;
1570    }
1571    // if(!supported) {
1572    //     ggml_et_dump_operator_metadata(op);
1573    // }
1574    return supported;
1575}
1576
1577static bool ggml_backend_et_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) {
1578    GGML_UNUSED(dev);
1579    return buft->iface.get_name == ggml_backend_et_buffer_type_get_name;
1580}
1581
1582static bool ggml_backend_et_device_offload_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
1583    // GET_ROWS (embedding lookup) uses a large weight (tok_embd) that lives on CPU (dev_input).
1584    // The scheduler has no mechanism to cache cross-backend weight copies - it re-copies split
1585    // inputs every graph_compute call. For GET_ROWS this means copying the entire embedding table
1586    // (e.g. 266MB for Llama 3.1 1B) from host to device on every token, just to look up a few rows.
1587    // Keep GET_ROWS on CPU and let the scheduler copy only the small result to the device.
1588    // The other backends either only offload if the tensor lives on device or is large enough to
1589    // justify the copy cost.
1590    if (op->op == GGML_OP_GET_ROWS) {
1591        return false;
1592    }
1593    return true;
1594
1595    GGML_UNUSED(dev);
1596}
1597
1598static const struct ggml_backend_i ggml_backend_et_i = {
1599    /* .get_name                = */ ggml_backend_et_get_name,
1600    /* .free                    = */ ggml_backend_et_free,
1601    /* .set_tensor_async        = */ ggml_backend_et_set_tensor_async,
1602    /* .get_tensor_async        = */ ggml_backend_et_get_tensor_async,
1603    /* .set_tensor_2d_async     = */ NULL,
1604    /* .get_tensor_2d_async     = */ NULL,
1605    /* .cpy_tensor_async        = */ NULL,
1606    /* .synchronize             = */ ggml_backend_et_synchronize,
1607    /* .graph_plan_create       = */ NULL,
1608    /* .graph_plan_free         = */ NULL,
1609    /* .graph_plan_update       = */ NULL,
1610    /* .graph_plan_compute      = */ NULL,
1611    /* .graph_compute           = */ ggml_backend_et_graph_compute,
1612    /* .event_record            = */ NULL,
1613    /* .event_wait              = */ NULL,
1614    /* .graph_optimize          = */ NULL,
1615};
1616
1617static const char * ggml_backend_et_device_get_name(ggml_backend_dev_t dev) {
1618    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1619    return dev_ctx->name.c_str();
1620}
1621
1622static const char * ggml_backend_et_device_get_description(ggml_backend_dev_t dev) {
1623    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1624    return dev_ctx->desc.c_str();
1625}
1626
1627static void ggml_backend_et_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) {
1628    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1629    // Currently getFreeMemory is not available on a runtime without server.
1630    // For now, report total memory as free.
1631    *free                                    = dev_ctx->total_mem;
1632    *total                                   = dev_ctx->total_mem;
1633}
1634
1635static enum ggml_backend_dev_type ggml_backend_et_device_get_type(ggml_backend_dev_t dev) {
1636    GGML_UNUSED(dev);
1637    return GGML_BACKEND_DEVICE_TYPE_GPU;
1638}
1639
1640static void ggml_backend_et_device_get_props(ggml_backend_dev_t dev, struct ggml_backend_dev_props * props) {
1641    GGML_UNUSED(dev);
1642    props->name        = ggml_backend_et_device_get_name(dev);
1643    props->description = ggml_backend_et_device_get_description(dev);
1644    props->type        = ggml_backend_et_device_get_type(dev);
1645    ggml_backend_et_device_get_memory(dev, &props->memory_free, &props->memory_total);
1646    props->device_id = NULL;  // No PCI device ID available
1647    props->caps      = {
1648        /* .async                 = */ true,
1649        /* .host_buffer           = */ false,
1650        /* .buffer_from_host_ptr  = */ false,
1651        /* .events                = */ false,
1652        /* .mmap_support          = */ true,
1653    };
1654}
1655
1656static ggml_backend_t ggml_backend_et_device_init_backend(ggml_backend_dev_t dev, const char * params) {
1657    GGML_UNUSED(params);
1658    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1659    return ggml_backend_et_init(dev_ctx->devidx);
1660}
1661
1662static ggml_backend_buffer_type_t ggml_backend_et_device_get_buffer_type(ggml_backend_dev_t dev) {
1663    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1664    return dev_ctx->buftype;
1665}
1666
1667static ggml_backend_buffer_type_t ggml_backend_et_device_get_host_buffer_type(ggml_backend_dev_t dev) {
1668    GGML_UNUSED(dev);
1669    return ggml_backend_cpu_buffer_type();
1670}
1671
1672static const struct ggml_backend_device_i ggml_backend_et_device_i = {
1673    /* .get_name          = */ ggml_backend_et_device_get_name,
1674    /* .get_description   = */ ggml_backend_et_device_get_description,
1675    /* .get_memory        = */ ggml_backend_et_device_get_memory,
1676    /* .get_type          = */ ggml_backend_et_device_get_type,
1677    /* .get_props         = */ ggml_backend_et_device_get_props,
1678    /* .init_backend      = */ ggml_backend_et_device_init_backend,
1679    /* .get_buffer_type   = */ ggml_backend_et_device_get_buffer_type,
1680    /* .get_host_buffer_type = */ ggml_backend_et_device_get_host_buffer_type,
1681    /* .buffer_from_host_ptr = */ NULL,
1682    /* .supports_op       = */ ggml_backend_et_device_supports_op,
1683    /* .supports_buft     = */ ggml_backend_et_device_supports_buft,
1684    /* .offload_op        = */ ggml_backend_et_device_offload_op,
1685    /* .event_new         = */ NULL,
1686    /* .event_free        = */ NULL,
1687    /* .event_synchronize = */ NULL,
1688};
1689
1690/*
1691  Backend Registry.
1692*/
1693
1694static const char * ggml_backend_et_reg_get_name(ggml_backend_reg_t reg) {
1695    GGML_UNUSED(reg);
1696    return GGML_ET_NAME;
1697}
1698
1699static size_t ggml_backend_et_reg_get_device_count(ggml_backend_reg_t reg) {
1700    ggml_backend_et_reg_ctx * ctx = (ggml_backend_et_reg_ctx *) reg->context;
1701    return ctx->devices.size();
1702}
1703
1704static ggml_backend_dev_t ggml_backend_et_reg_get_device(ggml_backend_reg_t reg, size_t devidx) {
1705    ggml_backend_et_reg_ctx * ctx = (ggml_backend_et_reg_ctx *) reg->context;
1706    if (devidx >= ctx->devices.size()) {
1707        return nullptr;
1708    }
1709    return ctx->devices[devidx];
1710}
1711
1712static void * ggml_backend_et_get_proc_address(ggml_backend_reg_t reg, const char * name) {
1713    GGML_UNUSED(reg);
1714    GGML_UNUSED(name);
1715    return nullptr;
1716}
1717
1718static const struct ggml_backend_reg_i ggml_backend_et_reg_i = {
1719    /* .get_name         = */ ggml_backend_et_reg_get_name,
1720    /* .get_device_count = */ ggml_backend_et_reg_get_device_count,
1721    /* .get_device       = */ ggml_backend_et_reg_get_device,
1722    /* .get_proc_address = */ ggml_backend_et_get_proc_address,
1723};
1724
1725ggml_backend_reg_t ggml_backend_et_reg(void) {
1726    static ggml_backend_reg_t _reg = []() -> ggml_backend_reg_t {
1727        ggml_backend_et_reg_ctx * ctx = new ggml_backend_et_reg_ctx;
1728
1729        if (!ggml_et_driver_init()) {
1730            return nullptr;
1731        }
1732
1733        ggml_backend_reg_t r = new ggml_backend_reg{
1734            /* .api_version = */ GGML_BACKEND_API_VERSION,
1735            /* .iface       = */ ggml_backend_et_reg_i,
1736            /* .context     = */ nullptr,  // Set later
1737        };
1738
1739        std::vector<rt::DeviceId> rtids = ggml_et_runtime()->getDevices();
1740
1741        for (int i = 0; i < ggml_et_devicelayer()->getDevicesCount(); i++) {
1742            ggml_backend_dev_t dev = new ggml_backend_device{
1743                /* .iface   = */ ggml_backend_et_device_i,
1744                /* .reg     = */ r,
1745                /* .context = */ nullptr  // Set later
1746            };
1747
1748            rt::DeviceId         rtid = rtids[i];
1749            rt::DeviceProperties prop = ggml_et_runtime()->getDeviceProperties(rtid);
1750
1751            // Create device context.
1752            ggml_backend_et_device_context * dev_ctx = new ggml_backend_et_device_context;
1753            dev_ctx->devidx                          = i;
1754            dev_ctx->rtid                            = rtid;
1755            dev_ctx->name                            = GGML_ET_NAME + std::to_string(i);
1756            dev_ctx->desc                            = "ET device " + std::to_string(i);
1757            dev_ctx->total_mem                       = static_cast<size_t>(prop.memorySize_);
1758            {
1759                const char * env            = getenv("GGML_ET_UBERKERNEL");
1760                dev_ctx->uberkernel_enabled = env && env[0] != '\0' && strcmp(env, "0") != 0;
1761            }
1762            // Add buffer type for device to device context.
1763            ggml_backend_et_buffer_type_context * bufty_ctx = new ggml_backend_et_buffer_type_context;
1764            bufty_ctx->devidx                               = i;
1765            bufty_ctx->name                                 = GGML_ET_NAME + std::to_string(i);
1766            dev_ctx->buftype = new ggml_backend_buffer_type{ /* .iface   = */ ggml_backend_et_buffer_type_i,
1767                                                             /* .device  = */ dev,
1768                                                             /* .context = */ bufty_ctx };
1769
1770            // Create default stream for ordered execution on this device
1771            dev_ctx->default_stream = ggml_et_runtime()->createStream(rtid);
1772
1773            dev_ctx->trace_buffer = ggml_et_runtime()->mallocDevice(rtid, ET_TRACE_BUFFER_SIZE);
1774            // Pre-size each slot's host buffers and device-side scratch so the
1775            // first few graph_compute calls don't pay a malloc/grow penalty.
1776            for (auto & slot : dev_ctx->uberkernel.slots) {
1777                slot.insts.reserve(256);
1778                slot.params_blob.reserve(1 << 20);
1779                slot.device_insts_capacity  = 256 * sizeof(ggml_et_uberkernel_inst);
1780                slot.device_params_capacity = 1 << 20;
1781                slot.device_insts           = ggml_et_runtime()->mallocDevice(rtid, slot.device_insts_capacity);
1782                slot.device_params          = ggml_et_runtime()->mallocDevice(rtid, slot.device_params_capacity);
1783                if (slot.device_insts == nullptr) {
1784                    slot.device_insts_capacity = 0;
1785                }
1786                if (slot.device_params == nullptr) {
1787                    slot.device_params_capacity = 0;
1788                }
1789            }
1790
1791            dev->context = dev_ctx;
1792
1793            ctx->devices.push_back(dev);
1794        }
1795
1796        r->context = ctx;
1797        return r;
1798    }();
1799
1800    return _reg;
1801}
1802
1803ggml_guid_t ggml_backend_et_guid(void) {
1804    static ggml_guid guid = { 0x4b, 0xe0, 0x72, 0x88, 0xc0, 0xf6, 0x29, 0xb4,
1805                              0x79, 0x9f, 0x70, 0x68, 0x71, 0x0f, 0x6d, 0xc8 };
1806    return &guid;
1807}
1808
1809ggml_backend_t ggml_backend_et_init(size_t devidx) {
1810    if (!ggml_et_driver_init()) {
1811        return nullptr;
1812    }
1813
1814    if (devidx >= (size_t) ggml_backend_et_get_device_count()) {
1815        return nullptr;
1816    }
1817
1818    ggml_backend_et_context * ctx = new ggml_backend_et_context;
1819    ctx->devidx                   = (int) devidx;
1820
1821    ggml_backend_t backend = new ggml_backend{
1822        /* .guid    = */ ggml_backend_et_guid(),
1823        /* .iface   = */ ggml_backend_et_i,
1824        /* .device  = */ ggml_backend_et_reg_get_device(ggml_backend_et_reg(), devidx),
1825        /* .context = */ ctx,
1826    };
1827
1828    return backend;
1829}
1830
1831bool ggml_backend_is_et(ggml_backend_t backend) {
1832    return backend != NULL && ggml_guid_matches(backend->guid, ggml_backend_et_guid());
1833}
1834
1835int ggml_backend_et_get_device_count(void) {
1836    return ggml_backend_et_reg_get_device_count(ggml_backend_et_reg());
1837}
1838
1839void ggml_backend_et_get_device_description(int devidx, char * description, size_t description_size) {
1840    if (devidx < 0 || devidx >= ggml_backend_et_get_device_count()) {
1841        snprintf(description, description_size, "ET Device %d (invalid)", devidx);
1842        return;
1843    }
1844
1845    ggml_backend_dev_t               dev     = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), devidx);
1846    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1847    snprintf(description, description_size, "%s", dev_ctx->desc.c_str());
1848}
1849
1850void ggml_backend_et_get_device_memory(int devidx, size_t * free, size_t * total) {
1851    if (devidx < 0 || devidx >= ggml_backend_et_get_device_count()) {
1852        *free  = 0;
1853        *total = 0;
1854        return;
1855    }
1856
1857    ggml_backend_dev_t dev = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), devidx);
1858    ggml_backend_et_device_get_memory(dev, free, total);
1859}
1860
1861ggml_backend_buffer_type_t ggml_backend_et_buffer_type(size_t dev_num) {
1862    if (dev_num >= (size_t) ggml_backend_et_get_device_count()) {
1863        return nullptr;
1864    }
1865
1866    ggml_backend_dev_t               dev     = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), dev_num);
1867    ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
1868    return dev_ctx->buftype;
1869}
1870
1871ggml_backend_buffer_type_t ggml_backend_et_host_buffer_type(void) {
1872    static ggml_backend_buffer_type host_buffer_type = {
1873        /* .iface   = */ ggml_backend_et_buffer_type_i,
1874        /* .device  = */ nullptr,
1875        /* .context = */ nullptr,
1876    };
1877    return &host_buffer_type;
1878}
1879
1880GGML_BACKEND_DL_IMPL(ggml_backend_et_reg)