63d2fc46e1
* model: add support for extra bufs for all devices * hexagon: add experimental ggml-hexagon backend for the Hexagon NPU This commit introduces a new experimental backend `ggml-hexagon` with support for the Hexagon NPU. Highlights: - Supports Hexagon versions: v73, v75, v79, and v81 - Targets Android devices based on Snapdragon SoCs: Gen3, 8-Elite, and 8-Elite Gen5 - Supports Q4_0, Q8_0, MXFP4, and FP32 data types - Implements core LLM ops: MUL_MAT/MUL_MAT_ID, ADD/SUB/MUL/ADD_ID, RMS_NORM, ROPE, GLU/SWIGLU, SOFTMAX **Note:** This backend is experimental and may exhibit instability or limited performance across supported devices. It is intended for early testing and feedback from llama.cpp/ggml developer and user community. Co-Authored-By: Rajdeep Ganguly <rganguly@qti.qualcomm.com> Co-Authored-By: Todor Boinovski <todorb@qti.qualcomm.com> * hexagon: fix format checker errors * hexagon: update readme and cmake presets * ci: add android-ndk-build jobs that build plain ARM64 and Snapdragon versions * hexagon: add simple graph optimizer for stacking MUL_MAT ops with the same input * hexagon: move ADB helper scripts into scripts/snapdragon/adb * hexagon: replace all f/printfs with GGML_LOG_... * readme: add hexagon to the list supported backends * hexagon: stack malmuts with quantized inputs only * hexagon: add TODO for fixing issues in hexagon_graph_optimize * hexagon: update to hex-sdk 6.4.0 and add scripts for running on QDC * scripts: fix lint errors * scripts: update qdc pytest script to make linter happy * hexagon: add reduce sum in fp32 * hexagon: reduce number of vector stores in matmul output * hexagon: remove the need for vdelta in reduce-multiply-x8 * hexagon: consistent use of reduce_sum_fp32 for row_sums * hexagon: some more matmul optimizations and comments Optimize cases where tensor dims are not multiple of 1024 (e.g in Qwen models). We've handled those cases already but at a higher overhead. * hexagon: update cmake presets * hexagon: add OPMASK support for run-bench.sh wrapper * hexagon: update to use GGML_BACKEND_API * hexagon: remove unused logic for setting tensor flags for the views * hexagon: add asserts to set/get_tensor to make sure we handle complete tensors Same asserts as the CPU backend. * hexagon: use cpy_tensor slow path for non-host buffers * hexagon: error checks in the buffer allocator * cmake: move include(extProj) under ggml-hexagon * hexagon: don't forget to delete the backend on free * hexagon: set/get_tensor size assert apply only to quantized tensors * hexagon: reintroduce HEX_VERBOSE wrapper for GGML_LOG_DEBUG for now GGML_LOG_DEBUG is always enabled for test-backend-ops and the output gets in the way. Ideally we need a bit more finer log levels. * docs: typos in hexagon developer docs (libggm-...) * hexagon: overhaul error handling in the session/device allocation this should handle all failure paths in the session allocation. * hexagon: update cmake presets to enable fp16 vectors * hexagon: remove unused time_usec function * hexagon: don't forget to release buffer contexts * hexagon: fixed indents in hvx-utils (missed clang-format auto-format failure) * hexagon: remove custom can_repeat function and use ggml_can_repeat --------- Co-authored-by: Rajdeep Ganguly <rganguly@qti.qualcomm.com> Co-authored-by: Todor Boinovski <todorb@qti.qualcomm.com>
345 lines
13 KiB
C
345 lines
13 KiB
C
#pragma clang diagnostic ignored "-Wunused-variable"
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
|
|
|
|
#ifdef HTP_DEBUG
|
|
# define FARF_HIGH 1
|
|
#endif
|
|
|
|
#include <HAP_farf.h>
|
|
#include <HAP_mem.h>
|
|
#include <HAP_perf.h>
|
|
#include <HAP_ps.h>
|
|
#include <hexagon_protos.h>
|
|
#include <hexagon_types.h>
|
|
#include <math.h>
|
|
#include <qurt_thread.h>
|
|
#include <string.h>
|
|
|
|
#define GGML_COMMON_DECL_C
|
|
#include "ggml-common.h"
|
|
#include "htp-ctx.h"
|
|
#include "htp-dma.h"
|
|
#include "htp-msg.h"
|
|
#include "htp-ops.h"
|
|
#include "hvx-utils.h"
|
|
#include "ops-utils.h"
|
|
|
|
typedef void (*hvx_elemwise_f32_func)(const uint8_t * src0,
|
|
const uint8_t * src1,
|
|
uint8_t * data_dst,
|
|
const int num_elems);
|
|
|
|
static hvx_elemwise_f32_func func_table_HVX[] = { hvx_mul_f32, hvx_add_f32, hvx_sub_f32 };
|
|
static hvx_elemwise_f32_func func_table_HVX_opt[] = { hvx_mul_f32_opt, hvx_add_f32_opt, hvx_sub_f32_opt };
|
|
|
|
#define htp_binary_preamble \
|
|
const uint32_t ne00 = src0->ne[0]; \
|
|
const uint32_t ne01 = src0->ne[1]; \
|
|
const uint32_t ne02 = src0->ne[2]; \
|
|
const uint32_t ne03 = src0->ne[3]; \
|
|
\
|
|
const uint32_t ne10 = src1->ne[0]; \
|
|
const uint32_t ne11 = src1->ne[1]; \
|
|
const uint32_t ne12 = src1->ne[2]; \
|
|
const uint32_t ne13 = src1->ne[3]; \
|
|
\
|
|
const uint32_t ne0 = dst->ne[0]; \
|
|
const uint32_t ne1 = dst->ne[1]; \
|
|
const uint32_t ne2 = dst->ne[2]; \
|
|
const uint32_t ne3 = dst->ne[3]; \
|
|
\
|
|
const uint32_t nb00 = src0->nb[0]; \
|
|
const uint32_t nb01 = src0->nb[1]; \
|
|
const uint32_t nb02 = src0->nb[2]; \
|
|
const uint32_t nb03 = src0->nb[3]; \
|
|
\
|
|
const uint32_t nb10 = src1->nb[0]; \
|
|
const uint32_t nb11 = src1->nb[1]; \
|
|
const uint32_t nb12 = src1->nb[2]; \
|
|
const uint32_t nb13 = src1->nb[3]; \
|
|
\
|
|
const uint32_t nb0 = dst->nb[0]; \
|
|
const uint32_t nb1 = dst->nb[1]; \
|
|
const uint32_t nb2 = dst->nb[2]; \
|
|
const uint32_t nb3 = dst->nb[3];
|
|
|
|
static void binary_job_f32_per_thread(const struct htp_tensor * src0,
|
|
const struct htp_tensor * src1,
|
|
struct htp_tensor * dst,
|
|
uint8_t * spad_data,
|
|
uint32_t nth,
|
|
uint32_t ith,
|
|
uint32_t src0_nrows_per_thread,
|
|
enum htp_op op) {
|
|
htp_binary_preamble;
|
|
|
|
const size_t src0_row_size = nb01;
|
|
const size_t src1_row_size = nb11;
|
|
const size_t dst_row_size = nb1;
|
|
|
|
const uint32_t src0_nrows = ne01 * ne02 * ne03; // src0 rows
|
|
const uint32_t src1_nrows = ne11 * ne12 * ne13; // src1 rows
|
|
|
|
const uint32_t src0_start_row = src0_nrows_per_thread * ith;
|
|
const uint32_t src0_end_row = MIN(src0_start_row + src0_nrows_per_thread, src0_nrows);
|
|
|
|
// no work for this thread
|
|
if (src0_start_row >= src0_end_row) {
|
|
return;
|
|
}
|
|
|
|
uint64_t t1, t2;
|
|
t1 = HAP_perf_get_qtimer_count();
|
|
|
|
int is_aligned = 1;
|
|
int opt_path = 0;
|
|
if ((0 == htp_is_aligned((void *) src0->data, VLEN)) || (0 == htp_is_aligned((void *) src1->data, VLEN)) ||
|
|
(0 == htp_is_aligned((void *) dst->data, VLEN))) {
|
|
FARF(HIGH, "binary-f32: unaligned addresses in elementwise op, possibly slower execution\n");
|
|
is_aligned = 0;
|
|
}
|
|
if ((1 == is_aligned) && !(nb01 & (VLEN - 1))) {
|
|
opt_path = 1;
|
|
}
|
|
|
|
hvx_elemwise_f32_func func_HVX = (1 == opt_path) ? func_table_HVX_opt[op] : func_table_HVX[op];
|
|
|
|
uint8_t * restrict spad_data_th = spad_data + (ith * src0_row_size);
|
|
|
|
const uint32_t nr0 = ne00 / ne10;
|
|
|
|
const uint8_t * restrict src0_ptr = (const uint8_t *) src0->data + (src0_start_row * src0_row_size);
|
|
uint8_t * restrict dst_ptr = (uint8_t *) dst->data + (src0_start_row * dst_row_size);
|
|
|
|
const uint8_t * restrict data_src1 = (const uint8_t *) src1->data;
|
|
const uint8_t * restrict src1_ptr = NULL;
|
|
|
|
for (uint32_t ir = src0_start_row; ir < src0_end_row; ir++) {
|
|
src1_ptr = data_src1 + (ir % src1_nrows) * src1_row_size;
|
|
|
|
if (ir + 1 < src0_end_row) {
|
|
htp_l2fetch(src0_ptr + ne00, 1, src0_row_size, src0_row_size);
|
|
if (src1_row_size == src0_row_size) {
|
|
htp_l2fetch(src1_ptr, 1, src1_row_size, src1_row_size);
|
|
}
|
|
}
|
|
|
|
if (nr0 > 1) {
|
|
if ((1 == is_aligned) && (nr0 == ne00)) {
|
|
hvx_bcast_fp32_a(spad_data_th, *(float *) src1_ptr, nr0);
|
|
} else {
|
|
for (uint32_t r = 0; r < nr0; r++) {
|
|
memcpy(spad_data_th + r * nb11, (const uint8_t *) src1_ptr, nb11);
|
|
}
|
|
}
|
|
func_HVX((const uint8_t *) src0_ptr, (const uint8_t *) spad_data_th, (uint8_t *) dst_ptr, ne00);
|
|
} else {
|
|
func_HVX((const uint8_t *) src0_ptr, (const uint8_t *) src1_ptr, (uint8_t *) dst_ptr, ne00);
|
|
}
|
|
|
|
src0_ptr += src0_row_size;
|
|
dst_ptr += dst_row_size;
|
|
}
|
|
|
|
t2 = HAP_perf_get_qtimer_count();
|
|
|
|
FARF(HIGH, "binary-f32 %d/%d/%d: %ux%ux%ux%u (%u:%u) x %ux%ux%ux%u -> %ux%ux%ux%u usec %u\n", ith, nth, opt_path,
|
|
ne00, ne01, ne02, ne03, src0_start_row, src0_end_row, ne10, ne11, ne12, ne13, ne0, ne1, ne2, ne3,
|
|
(unsigned) HAP_perf_qtimer_count_to_us(t2 - t1));
|
|
}
|
|
|
|
static void binary_add_id_job_f32_per_thread(const struct htp_tensor * src0,
|
|
const struct htp_tensor * src1,
|
|
const struct htp_tensor * src2,
|
|
struct htp_tensor * dst,
|
|
uint8_t * spad_data,
|
|
uint32_t nth,
|
|
uint32_t ith,
|
|
uint32_t src0_nrows_per_thread,
|
|
hvx_elemwise_f32_func func_HVX) {
|
|
htp_binary_preamble;
|
|
|
|
const size_t src0_row_size = nb01;
|
|
const size_t src1_row_size = nb11;
|
|
const size_t dst_row_size = nb1;
|
|
|
|
const uint32_t ne02_ne01 = ne02 * ne01;
|
|
const uint32_t src0_nrows = ne01 * ne02 * ne03; // src0 rows
|
|
|
|
const uint32_t src0_start_row = src0_nrows_per_thread * ith;
|
|
const uint32_t src0_end_row = MIN(src0_start_row + src0_nrows_per_thread, src0_nrows);
|
|
|
|
// no work for this thread
|
|
if (src0_start_row >= src0_end_row) {
|
|
return;
|
|
}
|
|
|
|
uint64_t t1, t2;
|
|
t1 = HAP_perf_get_qtimer_count();
|
|
|
|
if ((0 == htp_is_aligned((void *) src0->data, VLEN)) || (0 == htp_is_aligned((void *) src1->data, VLEN)) ||
|
|
(0 == htp_is_aligned((void *) dst->data, VLEN))) {
|
|
FARF(HIGH, "add-id-f32: unaligned addresses, possibly slower execution\n");
|
|
}
|
|
|
|
const uint8_t * restrict data_src0 = (const uint8_t *) src0->data;
|
|
const uint8_t * restrict data_src1 = (const uint8_t *) src1->data;
|
|
uint8_t * restrict data_dst = (uint8_t *) dst->data;
|
|
|
|
for (uint32_t ir = src0_start_row; ir < src0_end_row; ir++) {
|
|
// src0 indices
|
|
const uint32_t i03 = ir / ne02_ne01;
|
|
const uint32_t i02 = (ir - i03 * ne02_ne01) / ne01;
|
|
const uint32_t i01 = (ir - i03 * ne02_ne01 - i02 * ne01);
|
|
|
|
// src1 indices
|
|
const int i11 = *(int32_t *) ((char *) src2->data + i01 * src2->nb[0] + i02 * src2->nb[1]);
|
|
assert(i11 >= 0 && i11 < ne11);
|
|
|
|
float * restrict dst_ptr = (float *) (data_dst + i03 * nb3 + i02 * nb2 + i01 * nb1);
|
|
const float * restrict src0_ptr = (const float *) (data_src0 + i03 * nb03 + i02 * nb02 + i01 * nb01);
|
|
const float * restrict src1_ptr = (const float *) (data_src1 + 0 + 0 + i11 * nb11);
|
|
|
|
if (ir + 1 < src0_end_row) {
|
|
htp_l2fetch(src0_ptr + ne00, 1, src0_row_size, src0_row_size);
|
|
if (src1_row_size == src0_row_size) {
|
|
htp_l2fetch(src1_ptr + ne10, 1, src1_row_size, src1_row_size);
|
|
}
|
|
}
|
|
|
|
const uint32_t nr0 = ne00 / ne10;
|
|
if (nr0 > 1) {
|
|
for (uint32_t r = 0; r < nr0; r++) {
|
|
memcpy(spad_data + r * nb10, (const uint8_t *) src1_ptr, nb10);
|
|
}
|
|
func_HVX((const uint8_t *) src0_ptr, (const uint8_t *) spad_data, (uint8_t *) dst_ptr, ne00);
|
|
} else {
|
|
func_HVX((const uint8_t *) src0_ptr, (const uint8_t *) src1_ptr, (uint8_t *) dst_ptr, ne00);
|
|
}
|
|
}
|
|
|
|
t2 = HAP_perf_get_qtimer_count();
|
|
|
|
FARF(HIGH, "add-id-f32 %d/%d: %ux%ux%ux%u (%u:%u) x %ux%ux%ux%u (%ux%ux%ux%u) -> %ux%ux%ux%u usec %u\n", ith, nth,
|
|
src0->ne[0], src0->ne[1], src0->ne[2], src0->ne[3], src0_start_row, src0_end_row, src1->ne[0], src1->ne[1],
|
|
src1->ne[2], src1->ne[3], src2->ne[0], src2->ne[1], src2->ne[2], src2->ne[3], dst->ne[0], dst->ne[1],
|
|
dst->ne[2], dst->ne[3], (unsigned) HAP_perf_qtimer_count_to_us(t2 - t1));
|
|
}
|
|
|
|
static void binary_job_dispatcher_f32(unsigned int n, unsigned int i, void * data) {
|
|
struct htp_ops_context * octx = (struct htp_ops_context *) data;
|
|
|
|
switch (octx->op) {
|
|
case HTP_OP_MUL:
|
|
case HTP_OP_ADD:
|
|
case HTP_OP_SUB:
|
|
binary_job_f32_per_thread(&octx->src0, &octx->src1, &octx->dst, octx->src1_spad.data, n, i,
|
|
octx->src0_nrows_per_thread, octx->op);
|
|
break;
|
|
|
|
case HTP_OP_ADD_ID:
|
|
binary_add_id_job_f32_per_thread(&octx->src0, &octx->src1, &octx->src2, &octx->dst, octx->src0_spad.data, n,
|
|
i, octx->src0_nrows_per_thread, hvx_add_f32);
|
|
break;
|
|
|
|
default:
|
|
FARF(ERROR, "Unknown Binary Op %u", octx->op);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int execute_op_binary_f32(struct htp_ops_context * octx) {
|
|
int err = HTP_STATUS_OK;
|
|
|
|
const struct htp_tensor * src0 = &octx->src0;
|
|
const struct htp_tensor * src1 = &octx->src1;
|
|
struct htp_tensor * dst = &octx->dst;
|
|
|
|
worker_callback_t binary_op_func;
|
|
const char * op_type = NULL;
|
|
|
|
switch (octx->op) {
|
|
case HTP_OP_MUL:
|
|
binary_op_func = binary_job_dispatcher_f32;
|
|
op_type = "mul-f32";
|
|
break;
|
|
|
|
case HTP_OP_ADD:
|
|
binary_op_func = binary_job_dispatcher_f32;
|
|
op_type = "add-f32";
|
|
break;
|
|
|
|
case HTP_OP_SUB:
|
|
binary_op_func = binary_job_dispatcher_f32;
|
|
op_type = "sub-f32";
|
|
break;
|
|
|
|
case HTP_OP_ADD_ID:
|
|
binary_op_func = binary_job_dispatcher_f32;
|
|
op_type = "add-id-f32";
|
|
break;
|
|
|
|
default:
|
|
FARF(ERROR, "Unsupported binary-Op %u\n", octx->op);
|
|
return HTP_STATUS_NO_SUPPORT;
|
|
}
|
|
|
|
const int n_threads = octx->n_threads;
|
|
const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
|
|
|
|
const size_t src0_row_size = src0->nb[1];
|
|
const size_t src1_row_size = src1->nb[1];
|
|
const size_t dst_row_size = dst->nb[1];
|
|
|
|
// VTCM scratchpads for all tensors
|
|
octx->dst_spad.size = htp_round_up(dst_row_size, 128) * n_threads;
|
|
octx->src0_spad.size = htp_round_up(src0_row_size, 128) * n_threads;
|
|
octx->src1_spad.size = htp_round_up(src1_row_size, 128) * n_threads;
|
|
|
|
size_t spad_size = octx->src0_spad.size + octx->src1_spad.size + octx->dst_spad.size;
|
|
|
|
FARF(HIGH,
|
|
"%s: (%ux%ux%ux%u) * (%ux%ux%ux%u) -> (%ux%ux%ux%u) : src0-spad-size %u src1-spad-size %u dst-spad-size %u\n",
|
|
op_type, src0->ne[0], src0->ne[1], src0->ne[2], src0->ne[3], src1->ne[0], src1->ne[1], src1->ne[2],
|
|
src1->ne[3], dst->ne[0], dst->ne[1], dst->ne[2], dst->ne[3], octx->src0_spad.size, octx->src1_spad.size,
|
|
octx->dst_spad.size);
|
|
|
|
// Make sure the reserved vtcm size is sufficient
|
|
if (octx->ctx->vtcm_size < spad_size) {
|
|
FARF(ERROR, "binary-%s : current VTCM reservation %zu is too small, needed %zu\n", op_type,
|
|
octx->ctx->vtcm_size, spad_size);
|
|
return HTP_STATUS_VTCM_TOO_SMALL;
|
|
}
|
|
|
|
octx->src0_spad.data = octx->ctx->vtcm_base;
|
|
octx->src1_spad.data = octx->src0_spad.data + octx->src0_spad.size;
|
|
octx->dst_spad.data = octx->src1_spad.data + octx->src1_spad.size;
|
|
|
|
if (!(octx->flags & HTP_OPFLAGS_SKIP_COMPUTE)) {
|
|
uint32_t n_jobs = MIN(n_threads, src0_nrows);
|
|
|
|
octx->src0_nrows_per_thread = (src0_nrows + n_jobs - 1) / n_jobs;
|
|
|
|
worker_pool_run_func(octx->ctx->worker_pool, binary_op_func, octx, n_jobs);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int op_binary(struct htp_ops_context * octx) {
|
|
int err = HTP_STATUS_OK;
|
|
|
|
switch (octx->src0.type) {
|
|
case HTP_TYPE_F32:
|
|
err = execute_op_binary_f32(octx);
|
|
break;
|
|
|
|
default:
|
|
err = HTP_STATUS_NO_SUPPORT;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|