1878 lines
87 KiB
C++
1878 lines
87 KiB
C++
#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,
|
|
/* .mmap_support = */ true,
|
|
};
|
|
}
|
|
|
|
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)
|