kazutab's picture
Upload folder using huggingface_hub (part 4)
80e5f6d verified
Raw
History Blame Contribute Delete
88.6 kB
#include "ggml-et.h"
#include "ggml-backend-impl.h"
#include "ggml-backend.h"
#include "ggml-et-common.h"
#include "ggml-et-kernels.h"
#include "ggml-et-memops.h"
#include "ggml-et-ops.h"
#include "ggml-impl.h"
#include "ggml.h"
#include <stdarg.h>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#if __has_include(<filesystem>)
# include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
# include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
# error "cannot include the filesystem library"
#endif
/*
* ggml_et_dump_tensor_metadata
* @brief prints the metadata of a single tensorf
*/
static void ggml_et_dump_tensor_metadata(const ggml_tensor * ggtensor, size_t indent_level, const char * title) {
char * spaces = (char *) alloca(indent_level + 1);
memset(spaces, ' ', indent_level);
spaces[indent_level] = '\0';
fprintf(stderr,
"%s%s: %s\n"
"%s type: %s\n"
"%s ne: %lld %lld %lld %lld\n"
"%s nb: %zu %zu %zu %zu\n"
"%s op: %s\n"
"%s data: %p\n"
"%s src0: %p\n",
spaces, title, ggtensor->name, spaces, ggml_type_name(ggtensor->type), spaces, (long long) ggtensor->ne[0],
(long long) ggtensor->ne[1], (long long) ggtensor->ne[2], (long long) ggtensor->ne[3], spaces,
ggtensor->nb[0], ggtensor->nb[1], ggtensor->nb[2], ggtensor->nb[3], spaces, ggml_op_name(ggtensor->op),
spaces, ggtensor->data, spaces, (void *) ggtensor->src[0]);
}
/*
* ggml_et_dump_operator_metadata
* @brief prints the metadata of a single tensor (or operator) including it's input and views
*/
static void ggml_et_dump_operator_metadata(const ggml_tensor * ggtensor) {
GGML_ASSERT(ggtensor != NULL);
ggml_et_dump_tensor_metadata(ggtensor, 0, "GGML tensor");
for (int i = 0; i < GGML_MAX_SRC && ggtensor->src[i]; i++) {
char arr[16];
int n = snprintf(arr, sizeof(arr), "src[%i]->name", i);
GGML_ASSERT((unsigned) n < sizeof(arr) && "printed too much data to stack buffer");
ggml_et_dump_tensor_metadata(ggtensor->src[i], 2, arr);
}
if (ggtensor->view_src) {
ggml_et_dump_tensor_metadata(ggtensor, 2, "view_src");
}
}
static struct ggml_et_driver {
std::shared_ptr<dev::IDeviceLayer> device_layer;
std::shared_ptr<rt::IRuntime> runtime;
std::unique_ptr<std::ofstream> profile_stream;
std::unique_ptr<std::ofstream> kernel_id_stream;
std::vector<std::pair<std::string, rt::KernelId>> kernel_map;
bool profiling_enabled = false;
} _drv;
// Check at runtime environment variables for paths likely holding ET toolchain with sysemu elf files
static std::string ggml_et_get_default_et_path() {
// List of environment variables to check in order of preference
const char * const env_vars[] = { "ET_TOOLCHAIN", "TOOLCHAIN_ROOT" };
for (const char * var : env_vars) {
if (const char * et_path = std::getenv(var)) {
if (et_path && *et_path != '\0') {
return fs::path(et_path).string();
}
}
}
// Otherwise assume default
return fs::path("/opt/et").string();
}
// config when using sysemu instead of PCIe hardware device
// adapted from `ainekko/et-platform/esperanto-tools-libs/tools/src/bench.cpp`
static inline auto ggml_et_get_default_sysemu_options() {
constexpr uint64_t kSysEmuMaxCycles = std::numeric_limits<uint64_t>::max();
constexpr uint64_t kSysEmuMinionShiresMask = 0x1FFFFFFFFu;
const std::string et_path = ggml_et_get_default_et_path() + "/";
emu::SysEmuOptions sysEmuOptions;
// Construct all paths
sysEmuOptions.bootromTrampolineToBL2ElfPath =
et_path + "lib/esperanto-fw/BootromTrampolineToBL2/BootromTrampolineToBL2.elf";
sysEmuOptions.spBL2ElfPath =
et_path + "lib/esperanto-fw/ServiceProcessorBL2/fast-boot/ServiceProcessorBL2_fast-boot.elf";
sysEmuOptions.machineMinionElfPath = et_path + "lib/esperanto-fw/MachineMinion/MachineMinion.elf";
sysEmuOptions.masterMinionElfPath = et_path + "lib/esperanto-fw/MasterMinion/MasterMinion.elf";
sysEmuOptions.workerMinionElfPath = et_path + "lib/esperanto-fw/WorkerMinion/WorkerMinion.elf";
sysEmuOptions.executablePath = et_path + "bin/sys_emu";
// Check that each path has a valid existing non-zero file otherwise emulator just silently hangs
const std::vector<std::string> required_files = {
sysEmuOptions.bootromTrampolineToBL2ElfPath, sysEmuOptions.spBL2ElfPath,
sysEmuOptions.machineMinionElfPath, sysEmuOptions.masterMinionElfPath,
sysEmuOptions.workerMinionElfPath, sysEmuOptions.executablePath,
};
for (const auto & file : required_files) {
if (!fs::exists(file) || fs::file_size(file) == 0) {
// Check that each path has a valid existing non-zero file otherwise emulator just silently hangs
GGML_LOG_ERROR("ET: Unable to find required sysemu file: %s", file.c_str());
GGML_LOG_ERROR("ET: Confirm et-platform is correctly installed at configured path.");
abort();
}
}
sysEmuOptions.runDir = (fs::current_path().string() + "/");
sysEmuOptions.maxCycles = kSysEmuMaxCycles;
sysEmuOptions.minionShiresMask = kSysEmuMinionShiresMask;
sysEmuOptions.puUart0Path = sysEmuOptions.runDir + "pu_uart0_tx.log";
sysEmuOptions.puUart1Path = sysEmuOptions.runDir + "pu_uart1_tx.log";
sysEmuOptions.spUart0Path = sysEmuOptions.runDir + "spio_uart0_tx.log";
sysEmuOptions.spUart1Path = sysEmuOptions.runDir + "spio_uart1_tx.log";
sysEmuOptions.startGdb = false;
sysEmuOptions.memcheck = false;
return sysEmuOptions;
}
// Forward declaration
static void ggml_et_driver_cleanup();
static bool ggml_et_driver_init() {
if (_drv.runtime != nullptr) {
assert(_drv.device_layer != nullptr);
} else {
try {
#if defined GGML_ET_SYSEMU && GGML_ET_SYSEMU
// For emulator device using sysEmuOptions provided by function above enabled compiling with `-DGGML_ET_SYSEMU=ON`
_drv.device_layer = dev::IDeviceLayer::createSysEmuDeviceLayer(ggml_et_get_default_sysemu_options());
#else
// For physical PCIe device
_drv.device_layer = dev::IDeviceLayer::createPcieDeviceLayer();
#endif // GGML_ET_SYSEMU
_drv.runtime = rt::IRuntime::create(_drv.device_layer);
// Initialize profiler if requested via environment variable
const char * profile_path = getenv("GGML_ET_PROFILE");
if (profile_path) {
std::string output_path = std::string(profile_path) + "/et_runtime_trace.json";
std::string kernel_id_path = std::string(profile_path) + "/kernel_id.json";
_drv.profile_stream = std::make_unique<std::ofstream>(output_path);
_drv.kernel_id_stream = std::make_unique<std::ofstream>(kernel_id_path);
if (!_drv.profile_stream->is_open()) {
GGML_LOG_ERROR("ET: Failed to open profiling output file: %s", output_path.c_str());
abort();
}
if (!_drv.kernel_id_stream->is_open()) {
GGML_LOG_ERROR("ET: Failed to open profiling kernel map: %s", kernel_id_path.c_str());
abort();
}
auto * profiler = _drv.runtime->getProfiler();
profiler->start(*_drv.profile_stream, rt::IProfiler::OutputType::Json);
_drv.profiling_enabled = true;
GGML_LOG_INFO("ET: Runtime profiler started (JSON format)");
// Register cleanup at program exit
std::atexit(ggml_et_driver_cleanup);
}
} catch (const std::exception & e) {
GGML_LOG_ERROR("ggml_et: %s", e.what());
if (_drv.device_layer != nullptr) {
_drv.device_layer.reset();
}
if (_drv.runtime != nullptr) {
_drv.runtime.reset();
}
return false;
}
}
return true;
}
static std::shared_ptr<dev::IDeviceLayer> ggml_et_devicelayer() {
return _drv.device_layer;
}
std::shared_ptr<rt::IRuntime> ggml_et_runtime() {
return _drv.runtime;
}
static void ggml_et_driver_cleanup() {
if (_drv.profiling_enabled && _drv.runtime) {
GGML_LOG_INFO("ET: Stopping runtime profiler");
auto * profiler = _drv.runtime->getProfiler();
profiler->stop();
_drv.profiling_enabled = false;
if (_drv.profile_stream) {
_drv.profile_stream->close();
_drv.profile_stream.reset();
}
// Save kernel map
if (_drv.kernel_id_stream && !_drv.kernel_map.empty()) {
auto & os = *_drv.kernel_id_stream;
// XXX: Manual JSON construction. Not pretty but removes dependency
os << "{\n";
for (size_t i = 0; i < _drv.kernel_map.size(); i++) {
os << " \"" << _drv.kernel_map[i].first << "\": " << (int) _drv.kernel_map[i].second;
if (i + 1 < _drv.kernel_map.size()) {
os << ",";
}
os << "\n";
}
os << "}\n";
_drv.kernel_id_stream->close();
_drv.kernel_id_stream.reset();
}
}
}
static ggml_backend_dev_t ggml_backend_et_reg_get_device(ggml_backend_reg_t reg, size_t devidx);
static void ggml_backend_et_buffer_free_buffer(ggml_backend_buffer_t buffer) {
ggml_backend_et_buffer_context * ctx = (ggml_backend_et_buffer_context *) buffer->context;
if (ctx->data != nullptr) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (runtime) {
runtime->freeDevice(ctx->rtid, static_cast<std::byte *>(ctx->data));
}
}
delete ctx;
}
static void * ggml_backend_et_buffer_get_base(ggml_backend_buffer_t buffer) {
ggml_backend_et_buffer_context * ctx = (ggml_backend_et_buffer_context *) buffer->context;
return ctx->data;
}
static ggml_status ggml_backend_et_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
// View tensors share buffer with their view_src, no additional initialization needed
if (tensor->view_src != NULL) {
return GGML_STATUS_SUCCESS;
}
const size_t original_size = ggml_nbytes(tensor);
const size_t padded_size = ggml_backend_buft_get_alloc_size(buffer->buft, tensor);
// Clear padding bytes to avoid NaN values
// XXX: Martin - do we need this?
if (padded_size > original_size) {
const size_t padding_size = padded_size - original_size;
// Get device context to access memops kernel
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
if (!dev_ctx) {
GGML_LOG_ERROR("ET: Failed to get device context for padding clear");
return GGML_STATUS_FAILED;
}
// Use device-side memset kernel for efficient padding clear
std::byte * padding_ptr = static_cast<std::byte *>(tensor->data) + original_size;
if (!ggml_et_memset(dev_ctx, padding_ptr, 0, padding_size)) {
GGML_LOG_ERROR("ET: Failed to clear padding using memset kernel for tensor %s", tensor->name);
return GGML_STATUS_FAILED;
}
}
return GGML_STATUS_SUCCESS;
}
static void ggml_backend_et_buffer_set_tensor(ggml_backend_buffer_t buffer,
ggml_tensor * tensor,
const void * data,
size_t offset,
size_t size) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime) {
return;
}
// Create short-lived stream for this transfer
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
rt::StreamId stream = dev_ctx->default_stream;
std::byte * dst_ptr = static_cast<std::byte *>(tensor->data) + offset;
const std::byte * src_ptr = static_cast<const std::byte *>(data);
rt::EventId event = runtime->memcpyHostToDevice(stream, src_ptr, dst_ptr, size, true /*barrier*/);
runtime->waitForEvent(event);
}
static void ggml_backend_et_buffer_get_tensor(ggml_backend_buffer_t buffer,
const ggml_tensor * tensor,
void * data,
size_t offset,
size_t size) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime) {
return;
}
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
rt::StreamId stream = dev_ctx->default_stream;
const std::byte * src_ptr = static_cast<const std::byte *>(tensor->data) + offset;
std::byte * dst_ptr = static_cast<std::byte *>(data);
rt::EventId event = runtime->memcpyDeviceToHost(stream, src_ptr, dst_ptr, size, true /*barrier*/);
runtime->waitForEvent(event);
}
static bool ggml_backend_et_buffer_cpy_tensor(ggml_backend_buffer_t buffer,
const ggml_tensor * src,
ggml_tensor * dst) {
GGML_UNUSED(buffer);
GGML_UNUSED(src);
GGML_UNUSED(dst);
return false;
}
static void ggml_backend_et_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) {
ggml_backend_et_buffer_context * ctx = (ggml_backend_et_buffer_context *) buffer->context;
if (ctx->size == 0 || ctx->data == nullptr) {
return;
}
// Get device context to access memops kernel
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buffer->buft->device->context;
if (!dev_ctx) {
GGML_LOG_ERROR("ET: Failed to get device context for buffer clear");
return;
}
// Use device-side memset kernel for efficient clearing
if (!ggml_et_memset(dev_ctx, ctx->data, value, ctx->size)) {
GGML_LOG_ERROR("ET: buffer_clear failed using memset kernel");
return;
}
GGML_LOG_DEBUG("ET: Buffer cleared successfully using memops kernel");
}
static const struct ggml_backend_buffer_i ggml_backend_et_buffer_i = {
/* .free_buffer = */ ggml_backend_et_buffer_free_buffer,
/* .get_base = */ ggml_backend_et_buffer_get_base,
/* .init_tensor = */ ggml_backend_et_buffer_init_tensor,
/* .memset_tensor = */ NULL,
/* .set_tensor = */ ggml_backend_et_buffer_set_tensor,
/* .get_tensor = */ ggml_backend_et_buffer_get_tensor,
/* .set_tensor_2d = */ NULL,
/* .get_tensor_2d = */ NULL,
/* .cpy_tensor = */ ggml_backend_et_buffer_cpy_tensor,
/* .clear = */ ggml_backend_et_buffer_clear,
/* .reset = */ NULL,
};
static const char * ggml_backend_et_buffer_type_get_name(ggml_backend_buffer_type_t buft) {
GGML_UNUSED(buft);
return GGML_ET_NAME;
}
static ggml_backend_buffer_t ggml_backend_et_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
ggml_backend_et_buffer_type_context * btctx = (ggml_backend_et_buffer_type_context *) buft->context;
ggml_backend_et_buffer_context * ctx = new ggml_backend_et_buffer_context;
ctx->devidx = btctx->devidx;
ctx->size = size;
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime) {
delete ctx;
return nullptr;
}
std::vector<rt::DeviceId> rtids = runtime->getDevices();
if (static_cast<size_t>(btctx->devidx) >= rtids.size()) {
delete ctx;
return nullptr;
}
ctx->rtid = rtids[btctx->devidx];
ctx->data = runtime->mallocDevice(ctx->rtid, size);
if (ctx->data == nullptr) {
delete ctx;
return nullptr;
}
return ggml_backend_buffer_init(buft, ggml_backend_et_buffer_i, ctx, size);
}
static size_t ggml_backend_et_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime || !buft->device) {
return GGML_MEM_ALIGN;
}
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buft->device->context;
rt::DeviceProperties prop = runtime->getDeviceProperties(dev_ctx->rtid);
return prop.cacheLineSize_;
}
static size_t ggml_backend_et_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
if (buft->device) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) buft->device->context;
return dev_ctx->total_mem;
}
return SIZE_MAX;
}
static size_t ggml_backend_et_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
GGML_UNUSED(buft);
return ggml_nbytes_pad(tensor);
}
static bool ggml_backend_et_buffer_type_is_host(ggml_backend_buffer_type_t buft) {
GGML_UNUSED(buft);
return false;
}
static const struct ggml_backend_buffer_type_i ggml_backend_et_buffer_type_i = {
/* .get_name = */ ggml_backend_et_buffer_type_get_name,
/* .alloc_buffer = */ ggml_backend_et_buffer_type_alloc_buffer,
/* .get_alignment = */ ggml_backend_et_buffer_type_get_alignment,
/* .get_max_size = */ ggml_backend_et_buffer_type_get_max_size,
/* .get_alloc_size = */ ggml_backend_et_buffer_type_get_alloc_size,
/* .is_host = */ ggml_backend_et_buffer_type_is_host,
};
static const char * ggml_backend_et_get_name(ggml_backend_t backend) {
GGML_UNUSED(backend);
return GGML_ET_NAME;
}
static void ggml_backend_et_free(ggml_backend_t backend) {
ggml_backend_et_context * et_ctx = (ggml_backend_et_context *) backend->context;
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
// Clean up kernels on this device before freeing backend
ggml_backend_dev_t dev = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), et_ctx->devidx);
if (dev && dev->context) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
if (_drv.profiling_enabled) {
auto kernels = ggml_et_get_loaded_kernels(dev_ctx);
_drv.kernel_map.insert(_drv.kernel_map.end(), kernels.begin(), kernels.end());
}
ggml_et_unload_all_kernels(dev_ctx);
if (runtime) {
if (dev_ctx->trace_buffer) {
runtime->freeDevice(dev_ctx->rtid, dev_ctx->trace_buffer);
dev_ctx->trace_buffer = nullptr;
}
// Drain any in-flight uberkernel launches before freeing the
// device buffers they read from.
runtime->waitForStream(dev_ctx->default_stream);
for (auto & slot : dev_ctx->uberkernel.slots) {
if (slot.device_insts) {
runtime->freeDevice(dev_ctx->rtid, slot.device_insts);
slot.device_insts = nullptr;
}
if (slot.device_params) {
runtime->freeDevice(dev_ctx->rtid, slot.device_params);
slot.device_params = nullptr;
}
slot.has_pending = false;
}
}
}
delete et_ctx;
delete backend;
}
static ggml_backend_buffer_type_t ggml_backend_et_get_default_buffer_type(ggml_backend_t backend) {
ggml_backend_et_context * et_ctx = (ggml_backend_et_context *) backend->context;
return ggml_backend_et_buffer_type(et_ctx->devidx);
}
static void ggml_backend_et_set_tensor_async(ggml_backend_t backend,
ggml_tensor * tensor,
const void * data,
size_t offset,
size_t size) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime) {
return;
}
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
rt::StreamId stream = dev_ctx->default_stream;
std::byte * dst_ptr = static_cast<std::byte *>(tensor->data) + offset;
const std::byte * src_ptr = static_cast<const std::byte *>(data);
runtime->memcpyHostToDevice(stream, src_ptr, dst_ptr, size, true /*barrier*/);
}
static void ggml_backend_et_get_tensor_async(ggml_backend_t backend,
const ggml_tensor * tensor,
void * data,
size_t offset,
size_t size) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime) {
return;
}
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
rt::StreamId stream = dev_ctx->default_stream;
const std::byte * src_ptr = static_cast<const std::byte *>(tensor->data) + offset;
std::byte * dst_ptr = static_cast<std::byte *>(data);
runtime->memcpyDeviceToHost(stream, src_ptr, dst_ptr, size, true /*barrier*/);
}
static bool ggml_backend_et_cpy_tensor_async(ggml_backend_t backend_src,
ggml_backend_t backend_dst,
const ggml_tensor * src,
ggml_tensor * dst) {
GGML_UNUSED(backend_src);
GGML_UNUSED(backend_dst);
GGML_UNUSED(src);
GGML_UNUSED(dst);
return false;
}
static void ggml_backend_et_synchronize(ggml_backend_t backend) {
std::shared_ptr<rt::IRuntime> runtime = ggml_et_runtime();
if (!runtime) {
return;
}
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
runtime->waitForStream(dev_ctx->default_stream);
auto errors = runtime->retrieveStreamErrors(dev_ctx->default_stream);
if (errors.empty()) {
return;
}
for (const auto & err : errors) {
GGML_LOG_ERROR("ET: stream error detected at synchronization point. Code: %d,Type: %d\n", (int) err.errorCode_,
(int) err.errorContext_.value()[0].type_);
}
abort();
}
static bool ggml_et_can_fuse(const ggml_cgraph * cgraph, int node_idx, std::initializer_list<ggml_op> ops) {
if (!ggml_can_fuse(cgraph, node_idx, ops)) {
return false;
}
if (ops.size() == 2 && ops.begin()[0] == GGML_OP_MUL_MAT && ops.begin()[1] == GGML_OP_ADD) {
const ggml_tensor * mm = cgraph->nodes[node_idx];
const ggml_tensor * add = cgraph->nodes[node_idx + 1];
// Only Q8_0 weights x F32 activations -> F32 (the kernel that has
// the bias path). Other MM variants must wait for their own kernel
// bias support.
if (mm->type != GGML_TYPE_F32 || mm->src[0]->type != GGML_TYPE_Q8_0 || mm->src[1]->type != GGML_TYPE_F32) {
return false;
}
// ADD must be F32 and one of its operands must be the MM output.
if (add->type != GGML_TYPE_F32) {
return false;
}
if (add->src[0] != mm && add->src[1] != mm) {
return false;
}
const ggml_tensor * bias = (add->src[0] == mm) ? add->src[1] : add->src[0];
if (bias->type != GGML_TYPE_F32) {
return false;
}
// No broadcasting: bias shape must equal MM output shape.
for (int i = 0; i < GGML_MAX_DIMS; ++i) {
if (bias->ne[i] != mm->ne[i]) {
return false;
}
}
// Bias and dst must be contiguous and have identical strides - the
// kernel uses dst-style offset arithmetic against bias's nb[].
if (!ggml_is_contiguous(bias) || !ggml_is_contiguous(mm)) {
return false;
}
for (int i = 0; i < GGML_MAX_DIMS; ++i) {
if ((int64_t) bias->nb[i] != (int64_t) add->nb[i]) {
return false;
}
}
}
if (ops.size() == 2 && ops.begin()[0] == GGML_OP_RMS_NORM && ops.begin()[1] == GGML_OP_MUL) {
const ggml_tensor * rms_norm = cgraph->nodes[node_idx];
const ggml_tensor * mul = cgraph->nodes[node_idx + 1];
// ET only supports F32
if (rms_norm->src[0]->type != GGML_TYPE_F32 || mul->type != GGML_TYPE_F32) {
return false;
}
// Identify the weights tensor (the MUL operand that isn't rms_norm output)
const ggml_tensor * weights = (mul->src[0] == rms_norm) ? mul->src[1] : mul->src[0];
if (weights->type != GGML_TYPE_F32) {
return false;
}
// Both inputs must be contiguous (ET hardware requirement)
if (!ggml_is_contiguous(rms_norm->src[0]) || !ggml_is_contiguous_rows(weights)) {
return false;
}
// ET requires cache-aligned rows (ne[0] % 16 == 0)
if (rms_norm->src[0]->ne[0] % 16 != 0 || weights->ne[0] % 16 != 0) {
return false;
}
// Fused kernel doesn't handle dim-0 broadcasting
if (weights->ne[0] != rms_norm->src[0]->ne[0]) {
return false;
}
}
return true;
}
static ggml_status ggml_backend_et_graph_compute(ggml_backend_t backend, ggml_cgraph * cgraph) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) backend->device->context;
ggml_et_uberkernel_begin_graph(&dev_ctx->uberkernel);
for (int i = 0; i < cgraph->n_nodes; i++) {
ggml_tensor * node = cgraph->nodes[i];
if (node->op == GGML_OP_NONE || node->op == GGML_OP_VIEW || node->op == GGML_OP_RESHAPE ||
node->op == GGML_OP_PERMUTE || node->op == GGML_OP_TRANSPOSE) {
continue;
}
// --- Fusion checks (before regular dispatch) ---
if (ggml_et_can_fuse(cgraph, i, { GGML_OP_RMS_NORM, GGML_OP_MUL })) {
ggml_et_op_rms_norm_mul(dev_ctx, node, cgraph->nodes[i + 1]);
i++; // skip the MUL node
continue;
}
if (ggml_et_can_fuse(cgraph, i, { GGML_OP_MUL_MAT, GGML_OP_ADD })) {
ggml_et_op_mul_mat(dev_ctx, node, cgraph->nodes[i + 1]);
i++; // skip the ADD node
continue;
}
switch (node->op) {
case GGML_OP_SQR:
ggml_et_op_sqr(dev_ctx, node);
break;
case GGML_OP_UNARY:
ggml_et_op_unary(dev_ctx, node);
break;
case GGML_OP_SUM_ROWS:
ggml_et_op_sum_rows(dev_ctx, node);
break;
case GGML_OP_MEAN:
ggml_et_op_mean(dev_ctx, node);
break;
case GGML_OP_CLAMP:
ggml_et_op_clamp(dev_ctx, node);
break;
case GGML_OP_MUL:
ggml_et_op_mul(dev_ctx, node);
break;
case GGML_OP_ADD:
ggml_et_op_add(dev_ctx, node);
break;
case GGML_OP_SUB:
ggml_et_op_sub(dev_ctx, node);
break;
case GGML_OP_CUMSUM:
ggml_et_op_cumsum(dev_ctx, node);
break;
case GGML_OP_MUL_MAT:
ggml_et_op_mul_mat(dev_ctx, node);
break;
case GGML_OP_MUL_MAT_ID:
ggml_et_op_mul_mat_id(dev_ctx, node);
break;
case GGML_OP_ROPE:
ggml_et_op_rope(dev_ctx, node);
break;
case GGML_OP_RMS_NORM:
ggml_et_op_rms_norm(dev_ctx, node);
break;
case GGML_OP_NORM:
ggml_et_op_norm(dev_ctx, node);
break;
case GGML_OP_L2_NORM:
ggml_et_op_l2_norm(dev_ctx, node);
break;
case GGML_OP_GROUP_NORM:
ggml_et_op_group_norm(dev_ctx, node);
break;
case GGML_OP_SCALE:
ggml_et_op_scale(dev_ctx, node);
break;
case GGML_OP_GLU:
ggml_et_op_glu(dev_ctx, node);
break;
case GGML_OP_SOFT_MAX:
ggml_et_op_softmax(dev_ctx, node);
break;
case GGML_OP_IM2COL:
ggml_et_op_im2col(dev_ctx, node);
break;
case GGML_OP_CONV_2D:
ggml_et_op_conv_2d(dev_ctx, node);
break;
case GGML_OP_FLASH_ATTN_EXT:
ggml_et_op_flash_attn_ext(dev_ctx, node);
break;
case GGML_OP_GET_ROWS:
ggml_et_op_get_rows(dev_ctx, node);
break;
case GGML_OP_CONT:
ggml_et_op_cont(dev_ctx, node);
break;
case GGML_OP_CPY:
ggml_et_op_cpy(dev_ctx, node);
break;
case GGML_OP_CONCAT:
ggml_et_op_concat(dev_ctx, node);
break;
case GGML_OP_REPEAT:
ggml_et_op_repeat(dev_ctx, node);
break;
case GGML_OP_SSM_CONV:
ggml_et_op_ssm_conv(dev_ctx, node);
break;
case GGML_OP_SSM_SCAN:
ggml_et_op_ssm_scan(dev_ctx, node);
break;
case GGML_OP_PAD:
ggml_et_op_pad(dev_ctx, node);
break;
case GGML_OP_SET_ROWS:
ggml_et_op_set_rows(dev_ctx, node);
break;
case GGML_OP_FILL:
ggml_et_op_fill(dev_ctx, node);
break;
case GGML_OP_DIAG:
ggml_et_op_diag(dev_ctx, node);
break;
case GGML_OP_TRI:
ggml_et_op_tri(dev_ctx, node);
break;
case GGML_OP_SOLVE_TRI:
ggml_et_op_solve_tri(dev_ctx, node);
break;
case GGML_OP_SET:
ggml_et_op_set(dev_ctx, node);
break;
case GGML_OP_RWKV_WKV6:
ggml_et_op_rwkv_wkv6(dev_ctx, node);
break;
case GGML_OP_RWKV_WKV7:
ggml_et_op_rwkv_wkv7(dev_ctx, node);
break;
case GGML_OP_GATED_DELTA_NET:
ggml_et_op_gated_delta_net(dev_ctx, node);
break;
default:
ggml_et_uberkernel_abort_graph(&dev_ctx->uberkernel);
GGML_LOG_ERROR("ET: Unsupported operation in graph: %s", ggml_op_name(node->op));
return GGML_STATUS_FAILED;
}
if (ggml_et_uberkernel_failed(&dev_ctx->uberkernel)) {
ggml_et_uberkernel_abort_graph(&dev_ctx->uberkernel);
return GGML_STATUS_FAILED;
}
}
if (!ggml_et_uberkernel_end_graph(dev_ctx)) {
ggml_et_uberkernel_abort_graph(&dev_ctx->uberkernel);
return GGML_STATUS_FAILED;
}
return GGML_STATUS_SUCCESS;
}
// Check that elements within each row are contiguous (nb[0] == type_size).
// Higher-dim strides can be arbitrary - kernels navigate them via byte offsets.
static bool et_ggml_is_row_contiguous(const ggml_tensor * t) {
return t->nb[0] == ggml_type_size(t->type);
}
static bool ggml_backend_et_device_supports_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
GGML_UNUSED(dev);
bool supported = false;
switch (op->op) {
case GGML_OP_CUMSUM:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->src[0]->nb[0] == sizeof(float) && ggml_is_contiguous(op);
break;
case GGML_OP_SQR:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
break;
case GGML_OP_SUM_ROWS:
// dst has ne[0]=1, src0 row length must be cache-aligned
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->src[0]->ne[0] % 16 == 0 && ggml_is_contiguous(op->src[0]);
break;
case GGML_OP_MEAN:
// Kernel handles arbitrary ne00 (per-row alignment guard with
// scalar tail), so no row-length divisibility constraint here.
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op->src[0]);
break;
case GGML_OP_CLAMP:
// Element-wise; kernel distributes by cache lines and handles a
// scalar tail, so any contiguous F32 size is fine - including the
// 1x1x1x1 scalar case.
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
break;
case GGML_OP_UNARY:
// Only require dim-0 contiguity (nb[0] == sizeof(float)). Higher
// dims may be arbitrarily strided views; the kernel walks per-row
// using all four nb[] values. See unary_f32.c entry_point.
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_nelements(op) % 16 == 0 && op->nb[0] == sizeof(float) && op->src[0]->nb[0] == sizeof(float)) {
switch (ggml_get_unary_op(op)) {
case GGML_UNARY_OP_ABS:
case GGML_UNARY_OP_SGN:
case GGML_UNARY_OP_NEG:
case GGML_UNARY_OP_STEP:
case GGML_UNARY_OP_TANH:
case GGML_UNARY_OP_ELU:
case GGML_UNARY_OP_RELU:
case GGML_UNARY_OP_SIGMOID:
case GGML_UNARY_OP_GELU:
case GGML_UNARY_OP_GELU_QUICK:
case GGML_UNARY_OP_SILU:
case GGML_UNARY_OP_HARDSWISH:
case GGML_UNARY_OP_HARDSIGMOID:
case GGML_UNARY_OP_EXP:
case GGML_UNARY_OP_EXPM1:
case GGML_UNARY_OP_SOFTPLUS:
case GGML_UNARY_OP_GELU_ERF:
case GGML_UNARY_OP_FLOOR:
case GGML_UNARY_OP_CEIL:
case GGML_UNARY_OP_ROUND:
case GGML_UNARY_OP_TRUNC:
supported = true;
break;
default:
break;
}
}
break;
case GGML_OP_MUL:
case GGML_OP_ADD:
case GGML_OP_SUB:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && op->nb[0] == sizeof(float) &&
op->src[0]->nb[0] == sizeof(float) &&
(op->src[1]->nb[0] == sizeof(float) || op->src[1]->ne[0] == 1) &&
op->nb[1] == op->ne[0] * sizeof(float);
break;
case GGML_OP_MUL_MAT:
// Support Q8_0 x F32 -> F32, F16 x F32 -> F32, F16 x F16 -> F32, and F32 x F32 -> F32 matrix multiplication
// Stride requirements: first dimension must be contiguous for all tensors
if (op->type == GGML_TYPE_F32 &&
((op->src[0]->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32) ||
(op->src[0]->type == GGML_TYPE_F16 && op->src[1]->type == GGML_TYPE_F16)) &&
op->ne[0] % 16 == 0 && // dst row length for tensor-store path
op->src[0]->ne[1] % 16 == 0 && // m
op->src[0]->ne[0] % 16 == 0 && // k
ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
// Special path for the FP32 TensorFMA kernel
// Limitation - generic kernels can tolerate non-cache-aligned dst rows
// because they publish each output element atomically. The matrix
// engine path still uses tiled tensor stores, so keep dst rows aligned.
// The m edge is difficult to do because of the 4 conseqtive load hardware limitation
// And the k edge is impossible because that is encoded as `stride & 0xFFFFFFFFFFC0ULL` which becomes 0 for stride 16 (4x FP32) :(
// FIXME: Right now this overwrites the mul_mat_f32 kernel - whatever. Fix later. Demo code
supported = true;
} else if (op->type == GGML_TYPE_F32 && op->src[0] &&
(op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32) && op->src[1] &&
(op->src[1]->type == GGML_TYPE_F16 || op->src[1]->type == GGML_TYPE_F32)) {
// Check first dimension contiguity requirements
bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
bool dst_first_dim_contiguous = (op->nb[0] == sizeof(float));
// Check destination stride ordering (only for dimensions with ne > 1)
bool dst_properly_ordered = true;
for (int d = 0; d < 3; d++) {
if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
dst_properly_ordered = false;
}
}
supported = src0_first_dim_contiguous && src1_first_dim_contiguous && dst_first_dim_contiguous &&
dst_properly_ordered;
} else if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_Q8_0 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32) {
// Keep the existing quantized path constraints separate from the
// relaxed non-quant generic fallback.
bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
bool dst_first_dim_contiguous = (op->nb[0] == sizeof(float));
bool dst_properly_ordered = true;
for (int d = 0; d < 3; d++) {
if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
dst_properly_ordered = false;
}
}
supported = src0_first_dim_contiguous && src1_first_dim_contiguous && dst_first_dim_contiguous &&
dst_properly_ordered;
} else if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_Q4_0 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32) {
// Keep the existing quantized path constraints separate from the
// relaxed non-quant generic fallback.
bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
bool dst_first_dim_contiguous = (op->nb[0] == sizeof(float));
bool dst_properly_ordered = true;
for (int d = 0; d < 3; d++) {
if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
dst_properly_ordered = false;
}
}
supported = src0_first_dim_contiguous && src1_first_dim_contiguous && dst_first_dim_contiguous &&
dst_properly_ordered;
} else {
supported = false;
}
break;
case GGML_OP_MUL_MAT_ID:
// Support MUL_MAT_ID for Mixture of Experts: (Q8_0/Q4_0/F16/F32) x F32 -> F32 with I32 expert indices
// src0 (as): [K, M, n_expert] - expert weight matrices (can be quantized)
// src1 (b): [K, n_expert_used, batch] - activations (F32)
// src2 (ids): [n_expert_used, batch] - expert selection indices (I32)
// dst: [M, n_expert_used, batch, 1] - output (F32)
if (op->type == GGML_TYPE_F32 && op->src[0] &&
(op->src[0]->type == GGML_TYPE_Q8_0 || op->src[0]->type == GGML_TYPE_Q4_0 ||
op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_F32) &&
op->src[1] && op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_I32) {
// Check first dimension contiguity requirements (matching CPU backend)
bool src0_first_dim_contiguous = (op->src[0]->nb[0] == ggml_type_size(op->src[0]->type));
bool src1_first_dim_contiguous = (op->src[1]->nb[0] == ggml_type_size(op->src[1]->type));
bool src2_first_dim_contiguous = (op->src[2]->nb[0] == ggml_type_size(op->src[2]->type));
bool dst_first_dim_contiguous = (op->nb[0] == sizeof(float));
// Check destination stride ordering (only for dimensions with ne > 1)
bool dst_properly_ordered = true;
for (int d = 0; d < 3; d++) {
if (op->ne[d] > 1 && op->ne[d + 1] > 1 && op->nb[d] > op->nb[d + 1]) {
dst_properly_ordered = false;
}
}
// Validate tensor dimension constraints from GGML definition
bool dims_valid = (op->src[0]->ne[3] == 1) && // as is 3d (one matrix per expert)
(op->src[1]->ne[3] == 1) && // b is 3d
(op->src[2]->ne[2] == 1 && op->src[2]->ne[3] == 1) && // ids is 2d
(op->src[2]->ne[1] == op->src[1]->ne[2]) && // must have expert list per b row
(op->src[0]->ne[0] == op->src[1]->ne[0]) && // K dimension must match
(op->src[2]->ne[0] % op->src[1]->ne[1] == 0); // can broadcast
supported = src0_first_dim_contiguous && src1_first_dim_contiguous && src2_first_dim_contiguous &&
dst_first_dim_contiguous && dst_properly_ordered && dims_valid;
} else {
supported = false;
}
break;
case GGML_OP_ROPE:
// Support F32 x I32 -> F32 RoPE for the modes implemented by rope_f32.
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_I32 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0])) {
const int mode = ggml_get_op_params_i32(op, 2);
const int ndims = ggml_get_op_params_i32(op, 1);
const bool is_normal = mode == GGML_ROPE_TYPE_NORMAL;
const bool is_neox = mode == GGML_ROPE_TYPE_NEOX;
const bool is_imrope = mode == GGML_ROPE_TYPE_IMROPE;
const bool zero_view_offset = op->src[0]->view_src == nullptr || op->src[0]->view_offs == 0;
const bool has_sections = ggml_get_op_params_i32(op, 11) > 0 || ggml_get_op_params_i32(op, 12) > 0 ||
ggml_get_op_params_i32(op, 13) > 0;
supported =
zero_view_offset && ndims <= 512 &&
(is_normal || (is_neox && ndims % 16 == 0) || (is_imrope && ndims % 16 == 0 && has_sections));
} else {
supported = false;
}
break;
case GGML_OP_RMS_NORM:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]);
break;
case GGML_OP_NORM:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]);
break;
case GGML_OP_L2_NORM:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->ne[0] % 16 == 0 && ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]);
break;
case GGML_OP_GROUP_NORM:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && et_ggml_is_row_contiguous(op->src[0]) &&
ggml_get_op_params_i32(op, 0) > 0;
break;
case GGML_OP_IM2COL:
supported = op->src[0] && op->src[1] &&
((op->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32) ||
(op->type == GGML_TYPE_F16 &&
(op->src[1]->type == GGML_TYPE_F16 || op->src[1]->type == GGML_TYPE_F32))) &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[1]) &&
op->nb[0] == ggml_type_size(op->type) && op->src[1]->nb[0] == ggml_type_size(op->src[1]->type);
break;
case GGML_OP_CONV_2D:
{
// First-cut conv_2d_f32_me kernel constraints. Anything outside
// this falls back to CPU (it's a strict subset on purpose).
if (!op->src[0] || !op->src[1]) {
supported = false;
break;
}
if (op->type != GGML_TYPE_F32 || op->src[0]->type != GGML_TYPE_F32 ||
op->src[1]->type != GGML_TYPE_F32) {
supported = false;
break;
}
if (!ggml_is_contiguous(op) || !ggml_is_contiguous(op->src[0]) || !ggml_is_contiguous(op->src[1])) {
supported = false;
break;
}
const ggml_tensor * flt = op->src[0]; // [Kw, Kh, Cin, Cout]
const ggml_tensor * in = op->src[1]; // [W, H, Cin, N]
const int32_t s0 = ggml_get_op_params_i32(op, 0);
const int32_t s1 = ggml_get_op_params_i32(op, 1);
const int32_t p0 = ggml_get_op_params_i32(op, 2);
const int32_t p1 = ggml_get_op_params_i32(op, 3);
const int32_t d0 = ggml_get_op_params_i32(op, 4);
const int32_t d1 = ggml_get_op_params_i32(op, 5);
const int64_t Kw = flt->ne[0];
const int64_t Kh = flt->ne[1];
const int64_t Cin = flt->ne[2];
const int64_t Cout = flt->ne[3];
const int64_t H = in->ne[1];
(void) in->ne[0];
if (s0 < 1 || s1 < 1 || !(d0 == 1 && d1 == 1) || Cin % 16 != 0 || Cout % 16 != 0 || in->ne[3] != 1) {
supported = false;
break;
}
const int64_t OW = op->ne[0];
const int64_t OH = op->ne[1];
if (OW <= 0 || OH <= 0) {
supported = false;
break;
}
(void) p0;
(void) p1;
// Mirror the kernel's sizing:
// if K_TILES * per_KT_bytes <= budget: 1 buffer, n_chunks=1
// else: 2 buffers (double-buffer), shrink chunk_KT until
// 2*chunk_KT*per_KT_bytes <= budget.
const int64_t Hp = H + 2 * p1;
const int64_t OW_pad = (OW + 15) & ~15;
const int64_t Wp_a = OW_pad;
const bool need_stage = (OW % 16 != 0);
const int64_t stage_bytes = need_stage ? (Cout * OH * OW_pad * 4) : 0;
const int64_t L2SCP_BUDGET = 1500 * 1024;
// Per-hart partial-TenC scratch (mirrors kernel MAX_TILES_PER_HART=2):
// 32 minions x 2 tiles x 1024 bytes = 64 KB per shire.
const int64_t scratch_bytes = 32 * 2 * 16 * 16 * 4;
const int64_t budget = L2SCP_BUDGET - stage_bytes - scratch_bytes;
const int64_t per_KT_bytes = Kh * Kw * Cout * 16 * 4 + Kw * 16 * Hp * Wp_a * 4;
const int64_t K_TILES = Cin / 16;
int64_t chunk_KT_calc;
int64_t n_chunks_calc;
if (K_TILES * per_KT_bytes <= budget) {
chunk_KT_calc = K_TILES;
n_chunks_calc = 1;
} else {
chunk_KT_calc = K_TILES;
while (chunk_KT_calc > 1 && 2 * chunk_KT_calc * per_KT_bytes > budget) {
chunk_KT_calc--;
}
while (chunk_KT_calc > 1 && K_TILES % chunk_KT_calc != 0) {
chunk_KT_calc--;
}
if (chunk_KT_calc < 1) {
supported = false;
break;
}
n_chunks_calc = K_TILES / chunk_KT_calc;
}
if (n_chunks_calc > 1) {
const int64_t M_TILES = Cout / 16;
const int64_t w_tiles = (OW + 15) / 16;
const int64_t total_tiles = OH * w_tiles * M_TILES;
// MAX_TILES_PER_HART = 2 (mirrors kernel constant).
const int64_t max_workers = (need_stage ? 32 : 1024) * 2;
if (total_tiles > max_workers) {
supported = false;
break;
}
}
supported = true;
break;
}
case GGML_OP_SCALE:
// F32 contiguous, total elements must be cache line aligned (16 floats)
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && (ggml_nelements(op) % 16 == 0);
break;
case GGML_OP_GLU:
// Note: we only require row-wise contiguity (ggml_is_contiguous_1) so that
// strided views over a packed up_proj tensor (the common split-GLU layout)
// are accepted. The kernel walks rows via nb[1] strides, so the inner
// dimension just needs to be densely packed.
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_nelements(op) % 16 == 0 && ggml_is_contiguous_1(op) && ggml_is_contiguous_1(op->src[0])) {
// Check GLU variant - support SWIGLU, SWIGLU_OAI, GEGLU, GEGLU_ERF, GEGLU_QUICK, REGLU
ggml_glu_op glu_type = ggml_get_glu_op(op);
const bool supported_variant = glu_type == GGML_GLU_OP_SWIGLU || glu_type == GGML_GLU_OP_SWIGLU_OAI ||
glu_type == GGML_GLU_OP_GEGLU || glu_type == GGML_GLU_OP_GEGLU_ERF ||
glu_type == GGML_GLU_OP_GEGLU_QUICK || glu_type == GGML_GLU_OP_REGLU;
if (op->src[1]) {
supported = supported_variant && op->src[1]->type == GGML_TYPE_F32 &&
ggml_is_contiguous_1(op->src[1]) && op->src[0]->ne[0] == op->ne[0] &&
op->src[1]->ne[0] == op->ne[0];
} else {
supported = supported_variant && op->src[0]->ne[0] == 2 * op->ne[0];
}
} else {
supported = false;
}
break;
case GGML_OP_SOFT_MAX:
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && op->src[0]->ne[0] > 1) {
// Check optional mask tensor (F32 only)
if (op->src[1]) {
supported = op->src[1]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[1]);
if (!supported) {
break;
}
}
// Check optional sinks tensor (F32 only)
if (op->src[2]) {
supported = op->src[2]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[2]);
} else {
supported = true;
}
} else {
supported = false;
}
break;
case GGML_OP_SSM_SCAN:
supported = op->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && op->src[0] &&
op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguous(op->src[0]) && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op->src[2]) && op->src[3] && op->src[3]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op->src[3]) && op->src[4] && op->src[4]->type == GGML_TYPE_F32 &&
op->src[5] && op->src[5]->type == GGML_TYPE_F32 && op->src[6] &&
op->src[6]->type == GGML_TYPE_I32 && ggml_is_contiguous(op->src[6]) &&
op->src[1]->nb[0] == sizeof(float) && op->src[4]->nb[0] == sizeof(float) &&
op->src[5]->nb[0] == sizeof(float) &&
op->src[1]->nb[1] == (size_t) op->src[1]->ne[0] * sizeof(float) &&
op->src[4]->nb[1] == (size_t) op->src[4]->ne[0] * sizeof(float) &&
op->src[5]->nb[1] == (size_t) op->src[5]->ne[0] * sizeof(float) &&
op->src[0]->ne[0] == op->src[4]->ne[0] && op->src[0]->ne[1] == op->src[1]->ne[0] &&
op->src[0]->ne[2] == op->src[1]->ne[1] && op->src[1]->ne[2] == op->src[2]->ne[1] &&
op->src[1]->ne[3] == op->src[2]->ne[2] && op->src[4]->ne[2] == op->src[1]->ne[2] &&
op->src[4]->ne[3] == op->src[1]->ne[3] && ggml_are_same_shape(op->src[4], op->src[5]) &&
op->src[6]->ne[0] == op->src[1]->ne[3] && op->src[3]->ne[1] == op->src[1]->ne[1] &&
(op->src[3]->ne[0] == 1 || op->src[3]->ne[0] == op->src[0]->ne[0]) &&
(op->src[1]->ne[1] % op->src[4]->ne[1] == 0);
break;
case GGML_OP_FLASH_ATTN_EXT:
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
(op->src[1]->type == GGML_TYPE_F32 || op->src[1]->type == GGML_TYPE_F16) && op->src[2] &&
(op->src[2]->type == GGML_TYPE_F32 || op->src[2]->type == GGML_TYPE_F16) && op->src[4] == nullptr &&
ggml_is_contiguous_rows(op) && ggml_is_contiguous_rows(op->src[0])) {
float max_bias = 0.0f;
float logit_softcap = 0.0f;
memcpy(&max_bias, (const float *) op->op_params + 1, sizeof(max_bias));
memcpy(&logit_softcap, (const float *) op->op_params + 2, sizeof(logit_softcap));
const ggml_prec prec = ggml_flash_attn_ext_get_prec(op);
// Mask must be F16 or F32 if present
bool mask_ok = (op->src[3] == nullptr) || (op->src[3]->type == GGML_TYPE_F32) ||
(op->src[3]->type == GGML_TYPE_F16);
// GQA: n_head_q must be a multiple of n_head_kv
const int64_t nhq = op->src[0]->ne[2];
const int64_t nhk = op->src[1]->ne[2];
// K/V row stride must match element size
const size_t k_elem = op->src[1]->type == GGML_TYPE_F16 ? 2 : 4;
const size_t v_elem = op->src[2]->type == GGML_TYPE_F16 ? 2 : 4;
// Only support matrix engine path (F16 K/V, dk%32==0);
// mask scalar F32 fallback to get baseline perf readings
const bool me_eligible = op->src[1]->type == GGML_TYPE_F16 && op->src[2]->type == GGML_TYPE_F16 &&
(op->src[0]->ne[0] % 32) == 0;
supported = me_eligible && mask_ok && (prec == GGML_PREC_F32 || prec == GGML_PREC_DEFAULT) &&
max_bias == 0.0f && logit_softcap == 0.0f && op->src[0]->nb[0] == sizeof(float) &&
op->src[1]->nb[0] == k_elem && op->src[2]->nb[0] == v_elem && op->nb[0] == sizeof(float) &&
op->src[0]->ne[0] == op->src[1]->ne[0] && // dk matches
op->src[2]->ne[0] == op->ne[0] && // dv matches
op->src[2]->ne[0] <= 512 && // dv limit
op->src[0]->ne[0] <= 512 && // dk limit
nhq % nhk == 0 && // GQA ratio is integer
op->src[0]->ne[1] == op->ne[2] && op->src[0]->ne[2] == op->ne[1] &&
op->src[0]->ne[3] == op->ne[3] && op->src[1]->ne[1] == op->src[2]->ne[1] &&
op->src[1]->ne[2] == op->src[2]->ne[2] && op->src[1]->ne[3] == op->src[2]->ne[3] &&
op->src[0]->ne[3] == op->src[1]->ne[3];
} else {
supported = false;
}
break;
case GGML_OP_GET_ROWS:
// Support F32/F16/Q4_0/Q8_0/Q4_K data with I32 indices -> F32 output
if (op->type == GGML_TYPE_F32 && op->src[0] &&
(op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16 ||
op->src[0]->type == GGML_TYPE_Q4_0 || op->src[0]->type == GGML_TYPE_Q8_0 ||
op->src[0]->type == GGML_TYPE_Q4_K) &&
op->src[1] && op->src[1]->type == GGML_TYPE_I32 && ggml_is_contiguous(op) &&
ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
// Validate dimension constraints from ggml implementation
supported = (op->src[0]->ne[2] == op->src[1]->ne[1]) && (op->src[1]->ne[3] == 1);
} else {
supported = false;
}
break;
case GGML_OP_CONT:
// Support F32->F32 and F16->F16 CONT operations (rearrange non-contiguous to contiguous)
if ((op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && op->src[0] &&
op->src[0]->type == op->type && ggml_is_contiguous(op)) {
// Defensive check: ensure dst and src0 are not aliased (separate buffers)
// While GGML design currently guarantees this, check for future robustness
if (op->data && op->src[0]->data && op->data == op->src[0]->data) {
GGML_LOG_WARN("ET: CONT operation detected aliased tensors (dst == src0), unsupported");
supported = false;
} else {
supported = true;
}
} else {
supported = false;
}
break;
case GGML_OP_CPY:
// CPY copies src[0] data into dst layout (same as CONT for same-type)
// Special path: zero-element tensors (scalars) are accepted as no-ops
if (op->src[0]) {
const int64_t nelements = op->ne[0] * op->ne[1] * op->ne[2] * op->ne[3];
if (nelements == 0) {
// Zero-element / scalar no-op case - always supported
supported = true;
} else if ((op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && op->src[0]->type == op->type &&
ggml_is_contiguous(op)) {
// Same-type with contiguous dst - reuse CONT kernel
if (op->data && op->src[0]->data && op->data == op->src[0]->data) {
GGML_LOG_WARN("ET: CPY operation detected aliased tensors, unsupported");
supported = false;
} else {
supported = true;
}
} else if (op->type == GGML_TYPE_F16 && op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguous(op)) {
// F32 -> F16 conversion copy
supported = true;
} else {
supported = false;
}
} else {
supported = false;
}
break;
case GGML_OP_CONCAT:
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && ggml_is_contiguous(op)) {
const int32_t dim = ((const int32_t *) op->op_params)[0];
if (dim == 0 && op->src[0]->ne[0] % 16 == 0 && op->src[1]->ne[0] % 16 == 0 &&
ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
// Fast dim==0 path: both source row segments are cacheline-aligned
// and contiguous, so the kernel can use vector row copies.
supported = true;
} else if (dim == 0 && ((op->src[0]->nb[0] % sizeof(float) == 0) || op->src[0]->ne[0] == 1) &&
((op->src[1]->nb[0] % sizeof(float) == 0) || op->src[1]->ne[0] == 1)) {
// Slow dim==0 path: scalar, stride-aware copies for non-contiguous
// or non-aligned source row segments. Destination remains contiguous.
supported = true;
} else if (op->ne[0] % 16 == 0 && op->src[0]->ne[0] % 16 == 0 && op->src[1]->ne[0] % 16 == 0 &&
ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) {
// Dim >= 1 path: full aligned row copies from one source or the other.
supported = true;
}
}
break;
case GGML_OP_SSM_CONV:
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && op->src[0]->nb[0] == sizeof(float) &&
op->src[1]->nb[0] == sizeof(float) && op->src[0]->nb[1] == op->src[0]->ne[0] * sizeof(float) &&
op->src[1]->nb[1] == op->src[1]->ne[0] * sizeof(float) && ggml_is_contiguous(op) &&
op->src[1]->ne[1] == op->src[0]->ne[1] && op->ne[0] == op->src[0]->ne[1] &&
op->ne[1] == op->src[0]->ne[0] - op->src[1]->ne[0] + 1 && op->ne[2] == op->src[0]->ne[2];
break;
case GGML_OP_PAD:
// F32 zero-pad only, no dim0 padding, dst contiguous
// ne[0] must be CL-aligned (% 16 == 0) or evenly divide a CL (16 % ne[0] == 0)
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && (op->ne[0] % 16 == 0 || 16 % op->ne[0] == 0) &&
op->src[0]->nb[0] == sizeof(float)) {
const int32_t lp0 = ((const int32_t *) op->op_params)[0];
const int32_t rp0 = ((const int32_t *) op->op_params)[1];
const bool circular = (bool) ((const int32_t *) op->op_params)[8];
if (lp0 == 0 && rp0 == 0 && !circular) {
supported = true;
} else {
supported = false;
}
} else {
supported = false;
}
break;
case GGML_OP_REPEAT:
// Two acceptable shapes:
// 1. No-op REPEAT (src and dst have identical shape): dispatched
// to cont_f32, which handles arbitrary contiguous sizes.
// 2. Real REPEAT via repeat_f32 kernel: dst ne[0] cacheline-aligned,
// src0 ne[0] cacheline-aligned or 1, dst.ne[i] % src0.ne[i] == 0.
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && ggml_are_same_shape(op->src[0], op)) {
supported = true;
} else if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
(op->src[0]->ne[0] == 1 || op->src[0]->ne[0] % 16 == 0) && op->ne[0] % 16 == 0 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && op->ne[0] % op->src[0]->ne[0] == 0 &&
op->ne[1] % op->src[0]->ne[1] == 0 && op->ne[2] % op->src[0]->ne[2] == 0 &&
op->ne[3] % op->src[0]->ne[3] == 0) {
supported = true;
} else {
supported = false;
}
break;
case GGML_OP_FILL:
// F32 contiguous, ne[0] cacheline-aligned for SIMD fill
supported = op->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && op->ne[0] % 16 == 0;
break;
case GGML_OP_DIAG:
// F32 contiguous dst, src0 is 1D vector [N,1,...], dst is [N,N,...]
// ne[0] must be cacheline-aligned for SIMD zeroing
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
op->ne[0] % 16 == 0 && op->ne[0] == op->ne[1] && op->src[0]->ne[0] == op->ne[0] &&
op->src[0]->ne[1] == 1 && ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
break;
case GGML_OP_TRI:
// F32 contiguous, same shape in/out
// Kernel handles arbitrary ne[0] with aligned fast path + scalar fallback
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]);
break;
case GGML_OP_SOLVE_TRI:
// F32 contiguous, A square, shapes compatible
// Only lower-triangular left-side non-unit variant
// Require k % 16 == 0 for cache-line-safe column parallelism
supported = op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && op->src[0]->ne[0] == op->src[0]->ne[1] &&
op->src[0]->ne[1] == op->src[1]->ne[1] && op->src[1]->ne[0] % 16 == 0 &&
ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
break;
case GGML_OP_SET:
// Minimal useful support: inplace F32 SET of a contiguous src1 view into
// a contiguous dst/base tensor using explicit destination view strides.
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && ggml_is_contiguous(op->src[0]) &&
ggml_is_contiguous(op->src[1]) && ggml_are_same_shape(op, op->src[0]) && op->src[1]->ne[0] % 16 == 0) {
const bool inplace = (bool) ((const int32_t *) op->op_params)[4];
const size_t nb1 = ((const int32_t *) op->op_params)[0];
const size_t nb2 = ((const int32_t *) op->op_params)[1];
const size_t nb3 = ((const int32_t *) op->op_params)[2];
const size_t offset = ((const int32_t *) op->op_params)[3];
const size_t nb0 = ggml_element_size(op);
const size_t im0 = op->src[1]->ne[0] == 0 ? 0 : op->src[1]->ne[0] - 1;
const size_t im1 = op->src[1]->ne[1] == 0 ? 0 : op->src[1]->ne[1] - 1;
const size_t im2 = op->src[1]->ne[2] == 0 ? 0 : op->src[1]->ne[2] - 1;
const size_t im3 = op->src[1]->ne[3] == 0 ? 0 : op->src[1]->ne[3] - 1;
const bool view_bounds_ok = offset + im0 * nb0 + im1 * nb1 + im2 * nb2 + im3 * nb3 <= ggml_nbytes(op);
const bool cacheline_aligned =
(nb1 % 64 == 0) && (nb2 % 64 == 0) && (nb3 % 64 == 0) && (offset % 64 == 0);
supported = inplace && view_bounds_ok && cacheline_aligned;
}
break;
case GGML_OP_RWKV_WKV6:
// F32 contiguous, head_size must be multiple of 8 for vectorization
// 6 sources: k, v, r, tf, td, state
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_F32 && op->src[3] &&
op->src[3]->type == GGML_TYPE_F32 && op->src[4] && op->src[4]->type == GGML_TYPE_F32 && op->src[5] &&
op->src[5]->type == GGML_TYPE_F32 && op->src[0]->ne[0] % 8 == 0 && // head_size multiple of 8
ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]) && ggml_is_contiguous(op->src[2]) &&
ggml_is_contiguous(op->src[3]) && ggml_is_contiguous(op->src[4]) && ggml_is_contiguous(op->src[5])) {
supported = true;
} else {
supported = false;
}
break;
case GGML_OP_RWKV_WKV7:
// F32 contiguous, head_size must be multiple of 8 for vectorization
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] &&
op->src[1]->type == GGML_TYPE_F32 && op->src[2] && op->src[2]->type == GGML_TYPE_F32 && op->src[3] &&
op->src[3]->type == GGML_TYPE_F32 && op->src[4] && op->src[4]->type == GGML_TYPE_F32 && op->src[5] &&
op->src[5]->type == GGML_TYPE_F32 && op->src[6] && op->src[6]->type == GGML_TYPE_F32 &&
op->src[2]->ne[0] % 8 == 0 && // head_size multiple of 8
ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]) && ggml_is_contiguous(op->src[2]) &&
ggml_is_contiguous(op->src[3]) && ggml_is_contiguous(op->src[4]) && ggml_is_contiguous(op->src[5]) &&
ggml_is_contiguous(op->src[6])) {
supported = true;
} else {
supported = false;
}
break;
case GGML_OP_GATED_DELTA_NET:
// F32, S_v must be multiple of 8 for vectorization
// q, k, v may be row-contiguous with strided higher dimensions.
// g, beta, state stay contiguous.
if (op->type == GGML_TYPE_F32 && op->src[0] && op->src[0]->type == GGML_TYPE_F32 && // q
op->src[1] && op->src[1]->type == GGML_TYPE_F32 && // k
op->src[2] && op->src[2]->type == GGML_TYPE_F32 && // v
op->src[3] && op->src[3]->type == GGML_TYPE_F32 && // g
op->src[4] && op->src[4]->type == GGML_TYPE_F32 && // beta
op->src[5] && op->src[5]->type == GGML_TYPE_F32 && // state
op->src[2]->ne[0] % 8 == 0 && // S_v multiple of 8
(op->src[3]->ne[0] == 1 || op->src[3]->ne[0] == op->src[2]->ne[0]) && // g is scalar or per-element
op->src[4]->ne[0] == 1 && // beta is scalar per position
et_ggml_is_row_contiguous(op->src[0]) && et_ggml_is_row_contiguous(op->src[1]) &&
et_ggml_is_row_contiguous(op->src[2]) && ggml_is_contiguous(op->src[3]) &&
ggml_is_contiguous(op->src[4]) && ggml_is_contiguous(op->src[5])) {
supported = true;
} else {
supported = false;
}
break;
case GGML_OP_VIEW:
case GGML_OP_PERMUTE:
case GGML_OP_TRANSPOSE:
case GGML_OP_RESHAPE:
// Metadata-only no-ops, accept any type
supported = true;
break;
case GGML_OP_SET_ROWS:
// Support F32 data with I64 indices -> F16/F32 output (scatter operation)
if (op->src[0] && op->src[0]->type == GGML_TYPE_F32 && op->src[1] && op->src[1]->type == GGML_TYPE_I64 &&
(op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && ggml_is_contiguous_rows(op) &&
ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous(op->src[1])) {
// Validate dimension constraints from ggml implementation
supported = (op->ne[0] == op->src[0]->ne[0]) && // same number of columns
(op->ne[2] == op->src[0]->ne[2]) && // same batch size
(op->ne[3] == op->src[0]->ne[3]) && // same outer dimension
(op->src[0]->ne[1] == op->src[1]->ne[0]) && // src rows = index count
(op->src[0]->ne[2] % op->src[1]->ne[1] == 0) && // batch constraint
(op->src[0]->ne[3] % op->src[1]->ne[2] == 0) && // outer constraint
(op->src[1]->ne[3] == 1); // indices tensor constraint
} else {
supported = false;
}
break;
case GGML_OP_NONE:
// Always support NONE operations - they represent leaf nodes (parameters, inputs, constants)
// No computation needed, just memory management
supported = true;
break;
default:
supported = false;
break;
}
// if(!supported) {
// ggml_et_dump_operator_metadata(op);
// }
return supported;
}
static bool ggml_backend_et_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) {
GGML_UNUSED(dev);
return buft->iface.get_name == ggml_backend_et_buffer_type_get_name;
}
static bool ggml_backend_et_device_offload_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
// GET_ROWS (embedding lookup) uses a large weight (tok_embd) that lives on CPU (dev_input).
// The scheduler has no mechanism to cache cross-backend weight copies - it re-copies split
// inputs every graph_compute call. For GET_ROWS this means copying the entire embedding table
// (e.g. 266MB for Llama 3.1 1B) from host to device on every token, just to look up a few rows.
// Keep GET_ROWS on CPU and let the scheduler copy only the small result to the device.
// The other backends either only offload if the tensor lives on device or is large enough to
// justify the copy cost.
if (op->op == GGML_OP_GET_ROWS) {
return false;
}
return true;
GGML_UNUSED(dev);
}
static const struct ggml_backend_i ggml_backend_et_i = {
/* .get_name = */ ggml_backend_et_get_name,
/* .free = */ ggml_backend_et_free,
/* .set_tensor_async = */ ggml_backend_et_set_tensor_async,
/* .get_tensor_async = */ ggml_backend_et_get_tensor_async,
/* .set_tensor_2d_async = */ NULL,
/* .get_tensor_2d_async = */ NULL,
/* .cpy_tensor_async = */ NULL,
/* .synchronize = */ ggml_backend_et_synchronize,
/* .graph_plan_create = */ NULL,
/* .graph_plan_free = */ NULL,
/* .graph_plan_update = */ NULL,
/* .graph_plan_compute = */ NULL,
/* .graph_compute = */ ggml_backend_et_graph_compute,
/* .event_record = */ NULL,
/* .event_wait = */ NULL,
/* .graph_optimize = */ NULL,
};
static const char * ggml_backend_et_device_get_name(ggml_backend_dev_t dev) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
return dev_ctx->name.c_str();
}
static const char * ggml_backend_et_device_get_description(ggml_backend_dev_t dev) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
return dev_ctx->desc.c_str();
}
static void ggml_backend_et_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
// Currently getFreeMemory is not available on a runtime without server.
// For now, report total memory as free.
*free = dev_ctx->total_mem;
*total = dev_ctx->total_mem;
}
static enum ggml_backend_dev_type ggml_backend_et_device_get_type(ggml_backend_dev_t dev) {
GGML_UNUSED(dev);
return GGML_BACKEND_DEVICE_TYPE_GPU;
}
static void ggml_backend_et_device_get_props(ggml_backend_dev_t dev, struct ggml_backend_dev_props * props) {
GGML_UNUSED(dev);
props->name = ggml_backend_et_device_get_name(dev);
props->description = ggml_backend_et_device_get_description(dev);
props->type = ggml_backend_et_device_get_type(dev);
ggml_backend_et_device_get_memory(dev, &props->memory_free, &props->memory_total);
props->device_id = NULL; // No PCI device ID available
props->caps = {
/* .async = */ true,
/* .host_buffer = */ false,
/* .buffer_from_host_ptr = */ false,
/* .events = */ false,
};
}
static ggml_backend_t ggml_backend_et_device_init_backend(ggml_backend_dev_t dev, const char * params) {
GGML_UNUSED(params);
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
return ggml_backend_et_init(dev_ctx->devidx);
}
static ggml_backend_buffer_type_t ggml_backend_et_device_get_buffer_type(ggml_backend_dev_t dev) {
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
return dev_ctx->buftype;
}
static ggml_backend_buffer_type_t ggml_backend_et_device_get_host_buffer_type(ggml_backend_dev_t dev) {
GGML_UNUSED(dev);
return ggml_backend_cpu_buffer_type();
}
static const struct ggml_backend_device_i ggml_backend_et_device_i = {
/* .get_name = */ ggml_backend_et_device_get_name,
/* .get_description = */ ggml_backend_et_device_get_description,
/* .get_memory = */ ggml_backend_et_device_get_memory,
/* .get_type = */ ggml_backend_et_device_get_type,
/* .get_props = */ ggml_backend_et_device_get_props,
/* .init_backend = */ ggml_backend_et_device_init_backend,
/* .get_buffer_type = */ ggml_backend_et_device_get_buffer_type,
/* .get_host_buffer_type = */ ggml_backend_et_device_get_host_buffer_type,
/* .buffer_from_host_ptr = */ NULL,
/* .supports_op = */ ggml_backend_et_device_supports_op,
/* .supports_buft = */ ggml_backend_et_device_supports_buft,
/* .offload_op = */ ggml_backend_et_device_offload_op,
/* .event_new = */ NULL,
/* .event_free = */ NULL,
/* .event_synchronize = */ NULL,
};
/*
Backend Registry.
*/
static const char * ggml_backend_et_reg_get_name(ggml_backend_reg_t reg) {
GGML_UNUSED(reg);
return GGML_ET_NAME;
}
static size_t ggml_backend_et_reg_get_device_count(ggml_backend_reg_t reg) {
ggml_backend_et_reg_ctx * ctx = (ggml_backend_et_reg_ctx *) reg->context;
return ctx->devices.size();
}
static ggml_backend_dev_t ggml_backend_et_reg_get_device(ggml_backend_reg_t reg, size_t devidx) {
ggml_backend_et_reg_ctx * ctx = (ggml_backend_et_reg_ctx *) reg->context;
if (devidx >= ctx->devices.size()) {
return nullptr;
}
return ctx->devices[devidx];
}
static void * ggml_backend_et_get_proc_address(ggml_backend_reg_t reg, const char * name) {
GGML_UNUSED(reg);
GGML_UNUSED(name);
return nullptr;
}
static const struct ggml_backend_reg_i ggml_backend_et_reg_i = {
/* .get_name = */ ggml_backend_et_reg_get_name,
/* .get_device_count = */ ggml_backend_et_reg_get_device_count,
/* .get_device = */ ggml_backend_et_reg_get_device,
/* .get_proc_address = */ ggml_backend_et_get_proc_address,
};
ggml_backend_reg_t ggml_backend_et_reg(void) {
static ggml_backend_reg_t _reg = []() -> ggml_backend_reg_t {
ggml_backend_et_reg_ctx * ctx = new ggml_backend_et_reg_ctx;
if (!ggml_et_driver_init()) {
return nullptr;
}
ggml_backend_reg_t r = new ggml_backend_reg{
/* .api_version = */ GGML_BACKEND_API_VERSION,
/* .iface = */ ggml_backend_et_reg_i,
/* .context = */ nullptr, // Set later
};
std::vector<rt::DeviceId> rtids = ggml_et_runtime()->getDevices();
for (int i = 0; i < ggml_et_devicelayer()->getDevicesCount(); i++) {
ggml_backend_dev_t dev = new ggml_backend_device{
/* .iface = */ ggml_backend_et_device_i,
/* .reg = */ r,
/* .context = */ nullptr // Set later
};
rt::DeviceId rtid = rtids[i];
rt::DeviceProperties prop = ggml_et_runtime()->getDeviceProperties(rtid);
// Create device context.
ggml_backend_et_device_context * dev_ctx = new ggml_backend_et_device_context;
dev_ctx->devidx = i;
dev_ctx->rtid = rtid;
dev_ctx->name = GGML_ET_NAME + std::to_string(i);
dev_ctx->desc = "ET device " + std::to_string(i);
dev_ctx->total_mem = static_cast<size_t>(prop.memorySize_);
{
const char * env = getenv("GGML_ET_UBERKERNEL");
dev_ctx->uberkernel_enabled = env && env[0] != '\0' && strcmp(env, "0") != 0;
}
// Add buffer type for device to device context.
ggml_backend_et_buffer_type_context * bufty_ctx = new ggml_backend_et_buffer_type_context;
bufty_ctx->devidx = i;
bufty_ctx->name = GGML_ET_NAME + std::to_string(i);
dev_ctx->buftype = new ggml_backend_buffer_type{ /* .iface = */ ggml_backend_et_buffer_type_i,
/* .device = */ dev,
/* .context = */ bufty_ctx };
// Create default stream for ordered execution on this device
dev_ctx->default_stream = ggml_et_runtime()->createStream(rtid);
dev_ctx->trace_buffer = ggml_et_runtime()->mallocDevice(rtid, ET_TRACE_BUFFER_SIZE);
// Pre-size each slot's host buffers and device-side scratch so the
// first few graph_compute calls don't pay a malloc/grow penalty.
for (auto & slot : dev_ctx->uberkernel.slots) {
slot.insts.reserve(256);
slot.params_blob.reserve(1 << 20);
slot.device_insts_capacity = 256 * sizeof(ggml_et_uberkernel_inst);
slot.device_params_capacity = 1 << 20;
slot.device_insts = ggml_et_runtime()->mallocDevice(rtid, slot.device_insts_capacity);
slot.device_params = ggml_et_runtime()->mallocDevice(rtid, slot.device_params_capacity);
if (slot.device_insts == nullptr) {
slot.device_insts_capacity = 0;
}
if (slot.device_params == nullptr) {
slot.device_params_capacity = 0;
}
}
dev->context = dev_ctx;
ctx->devices.push_back(dev);
}
r->context = ctx;
return r;
}();
return _reg;
}
ggml_guid_t ggml_backend_et_guid(void) {
static ggml_guid guid = { 0x4b, 0xe0, 0x72, 0x88, 0xc0, 0xf6, 0x29, 0xb4,
0x79, 0x9f, 0x70, 0x68, 0x71, 0x0f, 0x6d, 0xc8 };
return &guid;
}
ggml_backend_t ggml_backend_et_init(size_t devidx) {
if (!ggml_et_driver_init()) {
return nullptr;
}
if (devidx >= (size_t) ggml_backend_et_get_device_count()) {
return nullptr;
}
ggml_backend_et_context * ctx = new ggml_backend_et_context;
ctx->devidx = (int) devidx;
ggml_backend_t backend = new ggml_backend{
/* .guid = */ ggml_backend_et_guid(),
/* .iface = */ ggml_backend_et_i,
/* .device = */ ggml_backend_et_reg_get_device(ggml_backend_et_reg(), devidx),
/* .context = */ ctx,
};
return backend;
}
bool ggml_backend_is_et(ggml_backend_t backend) {
return backend != NULL && ggml_guid_matches(backend->guid, ggml_backend_et_guid());
}
int ggml_backend_et_get_device_count(void) {
return ggml_backend_et_reg_get_device_count(ggml_backend_et_reg());
}
void ggml_backend_et_get_device_description(int devidx, char * description, size_t description_size) {
if (devidx < 0 || devidx >= ggml_backend_et_get_device_count()) {
snprintf(description, description_size, "ET Device %d (invalid)", devidx);
return;
}
ggml_backend_dev_t dev = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), devidx);
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
snprintf(description, description_size, "%s", dev_ctx->desc.c_str());
}
void ggml_backend_et_get_device_memory(int devidx, size_t * free, size_t * total) {
if (devidx < 0 || devidx >= ggml_backend_et_get_device_count()) {
*free = 0;
*total = 0;
return;
}
ggml_backend_dev_t dev = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), devidx);
ggml_backend_et_device_get_memory(dev, free, total);
}
ggml_backend_buffer_type_t ggml_backend_et_buffer_type(size_t dev_num) {
if (dev_num >= (size_t) ggml_backend_et_get_device_count()) {
return nullptr;
}
ggml_backend_dev_t dev = ggml_backend_et_reg_get_device(ggml_backend_et_reg(), dev_num);
ggml_backend_et_device_context * dev_ctx = (ggml_backend_et_device_context *) dev->context;
return dev_ctx->buftype;
}
ggml_backend_buffer_type_t ggml_backend_et_host_buffer_type(void) {
static ggml_backend_buffer_type host_buffer_type = {
/* .iface = */ ggml_backend_et_buffer_type_i,
/* .device = */ nullptr,
/* .context = */ nullptr,
};
return &host_buffer_type;
}
GGML_BACKEND_DL_IMPL(ggml_backend_et_reg)