* hexagon: introduce op request batching and rewrite buffer managment The host now prepares batches of requests and dispatches them via a single dspqueue message. Buffers are mapped explicitly by NPU while processing batches. * hex-dma: disable l2 bypass since to work around new issue due to no flushes between Ops * hex-utils: add explicit l2flush and l2clear helpers * hex-opreq: use fine-grain per tensor l2 management * hex-opreq: avoid redundant invalidates for tensors we already flushed * hex-opreq: update debug messages * htp-opreq: reuse ops_context * hex-opreq: do not flush or invalidate cache lines beyond buffer boundry * hex-opreq: fix errors in log message * Revert "hex-opreq: do not flush or invalidate cache lines beyond buffer boundry" This reverts commit 8b7f0a55a750a6430ce4eb1874c7feb3d720056d. * hexagon: limit l2 flushes to 1MB which covers l2 cache * hex-opreq: limit cache flush to 4MB Looks like 4MB cont. vitual space should cover the 1MB cache. * hexagon: drop cache flush size to 2MB * hex-opreq: start reworking opreq packing * hex-opreq: introduce new way of packing opbatch where tensors are stored separately * hex-opreq: add a simple fastrpc call to force unmap all buffers * hex-l2flush: somehow 2MB does not seem robust, also cleanup step size to use line-size * hex-opreq: bump opreq batch size to 256 * hex-mm: place src1 spad at the top of vtcm for easy reuse * hex-ops: introduce internal types and disable src1 reuse for now Nothing new just formalizing the repack / qyn.quant types we've been using. * htp-opreq: use tensor pointers instead of copies * hex-opreq: introduce more robust way for tracking vtcm/spad reuse This removes the SKIP_QUANTIZE flag that became fragile with the addition of HMX and other ops. * hex-cumsum: fix error post opreq merge * hex-opreq: move request batch handling into the session Prepping everything for using dspqueue buffers and doing that inside the session is much cleaner. * hex-mm: yet another fix for src1 reuse when we're mixing hmx/hvx * hex-bufs: introduce pinned mmapings and use non-pinned ones for model buffers * hex-buf: add support for allocating shared/pinned buffer for opreqs * hex-opbatch: make opbatches configurable * hex-naming: better name for ggml_hexagon_shared_buffer * hex-naming: add session->c_name() helper * hex-opbatch: start using shm but still copy for now * hex-opbatch: use shared buffer for packing opbatch * hex-opbatch: beter naming for opbatch related classes and code * hex-opbatch: reuse batched tensors with same data/dims/strides * hex-opbatch: update logging * hex-opbatch: add support for vmem limit for op batching * hex-opbatch: update htp side to properly support dynamic mmap/unmap * hex-opbatch: add OB and OQ params for run-completion script and fix the asserts in batch processing * hex-opbatch: fixed src1 handling in act ops * hex-act: fix empty src1 handling in swiglu and friends Simplify preamble macro while at it * hex-mm: minor fix vtcm and dma handling in matmul cleaning up some left-overs from merges * hex-opbatch: allocate extra 1KB for dspqueue overhead * hexagon: fix softmax for non-aligned tensors and cleanup vtcm alloc * hex-mm: properly handle hmx_disabled flag * hex-ops: update comments * hex-ops: add debug output for get/set-rows * hex-mmap: optimize un/mapping of buffers * hex-opreq: global cache flush and invalidate beyond 128KB threshold * hex-ops: add super simple opfilter regex for debugging If an Op matches the regex hex backend will reject it. * hex-opbatch: wireup newer ops missed in merge and update main switch to detect this in future * hexagon: improved vtcm acquision to remove inter-op overhead Fully compatible with QNN-HTP coex * hex-mm: fixed hvx fallback path * hex-mm: lower the vmem threshold a bit further to ~3GB * hexagon: update debug & error logs This also fixes an issue with newer llvm merging repack and non-repack functions. We use those pointer to distinguish between buffer types. * hexagon: move ops context into main context Just a cleanup. We don't need separate contexts at this point. * hex-opbatch: cleanup naming and headers for opbatch and related descriptors * hex-fa: it's now better to enable FA during TG to reduce graph splits * hexagon: remove GGML_HEXAGON_EXPERIMENTAL env var It's no longer useful. Please use more flexible GGML_HEXAGON_OPFILTER to disable Ops if needed for debugging or validation. * hexagon: fixed editorconfig check * Update ggml/src/ggml-hexagon/ggml-hexagon.cpp Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com> --------- Co-authored-by: Trivikram Reddy <tamarnat@qti.qualcomm.com> Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>
253 lines
8.8 KiB
C
253 lines
8.8 KiB
C
#pragma clang diagnostic ignored "-Wunused-variable"
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
|
|
|
|
#include <HAP_farf.h>
|
|
#include <HAP_perf.h>
|
|
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#define GGML_COMMON_DECL_C
|
|
#include "ggml-common.h"
|
|
#include "htp-ctx.h"
|
|
#include "htp-ops.h"
|
|
#include "htp-ops.h"
|
|
#include "hvx-utils.h"
|
|
|
|
struct htp_copy_context {
|
|
struct htp_ops_context * octx;
|
|
|
|
uint32_t src0_type_size;
|
|
uint32_t src0_block_size;
|
|
|
|
uint32_t dst_type_size;
|
|
uint32_t dst_block_size;
|
|
|
|
uint32_t src0_blocks_per_row;
|
|
uint32_t dst_blocks_per_row;
|
|
|
|
uint32_t src0_nrows_per_thread;
|
|
|
|
void (*copy)(struct htp_copy_context * ct, struct htp_ops_context * octx, int nth, int ith);
|
|
};
|
|
|
|
#define cpy_preamble \
|
|
const struct htp_tensor *src0 = octx->src[0]; \
|
|
const struct htp_tensor *dst = octx->dst; \
|
|
\
|
|
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 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 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 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]; \
|
|
\
|
|
const uint32_t nr = ne01;
|
|
|
|
static void cpy_thread_sametype_sameshape(struct htp_copy_context * ct, struct htp_ops_context * octx, const int nth, const int ith) {
|
|
cpy_preamble;
|
|
|
|
// parallelize by src0 rows
|
|
const uint32_t dr = ct->src0_nrows_per_thread;
|
|
const uint32_t ir0 = dr * ith;
|
|
const uint32_t ir1 = (ir0 + dr) < nr ? (ir0 + dr) : nr;
|
|
|
|
// copy by rows
|
|
for (uint32_t i03 = 0; i03 < ne03; i03++) {
|
|
for (uint32_t i02 = 0; i02 < ne02; i02++) {
|
|
#pragma unroll(2)
|
|
for (uint32_t i01 = ir0; i01 < ir1; i01++) {
|
|
uint8_t* dst_ptr = (uint8_t*) dst->data + i01*nb1 + i02*nb2 + i03*nb3;
|
|
uint8_t* src0_ptr = (uint8_t*) src0->data + i01*nb01 + i02*nb02 + i03*nb03;
|
|
hex_l2fetch(src0_ptr, ne00 * ct->src0_type_size, nb01, 2);
|
|
hvx_copy_uu(dst_ptr, src0_ptr, ne00, ct->src0_type_size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cpy_thread_sametype_reshape(struct htp_copy_context * ct, struct htp_ops_context * octx, int nth, int ith) {
|
|
cpy_preamble;
|
|
|
|
// parallelize by src0 rows
|
|
const uint32_t dr = ct->src0_nrows_per_thread;
|
|
const uint32_t ir0 = dr * ith;
|
|
const uint32_t ir1 = (ir0 + dr) < nr ? (ir0 + dr) : nr;
|
|
|
|
// dst counters
|
|
int64_t k10 = 0;
|
|
int64_t i11 = 0;
|
|
int64_t i12 = 0;
|
|
int64_t i13 = 0;
|
|
|
|
// number of blocks in a row
|
|
const int64_t nk00 = ct->src0_blocks_per_row;
|
|
const int64_t nk0 = ct->dst_blocks_per_row;
|
|
|
|
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
|
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
|
k10 += nk00 * ir0;
|
|
while (k10 >= nk0) {
|
|
k10 -= nk0;
|
|
if (++i11 == ne1) {
|
|
i11 = 0;
|
|
if (++i12 == ne2) {
|
|
i12 = 0;
|
|
if (++i13 == ne3) {
|
|
i13 = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (int64_t i01 = ir0; i01 < ir1; i01++) {
|
|
for (int64_t k00 = 0; k00 < nk00; k00++) {
|
|
const char * src0_ptr = ((char *) src0->data + k00*nb00 + i01*nb01 + i02*nb02 + i03*nb03);
|
|
char * dst_ptr = ((char *) dst->data + k10*nb0 + i11*nb1 + i12*nb2 + i13*nb3);
|
|
memcpy(dst_ptr, src0_ptr, ct->dst_type_size);
|
|
|
|
if (++k10 == nk0) {
|
|
k10 = 0;
|
|
if (++i11 == ne1) {
|
|
i11 = 0;
|
|
if (++i12 == ne2) {
|
|
i12 = 0;
|
|
if (++i13 == ne3) {
|
|
i13 = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
k10 += nk00 * (ne01 - ir1);
|
|
while (k10 >= nk0) {
|
|
k10 -= nk0;
|
|
if (++i11 == ne1) {
|
|
i11 = 0;
|
|
if (++i12 == ne2) {
|
|
i12 = 0;
|
|
if (++i13 == ne3) {
|
|
i13 = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cpy_thread_f16_f32_sameshape(struct htp_copy_context * ct, struct htp_ops_context * octx, const int nth, const int ith) {
|
|
cpy_preamble;
|
|
|
|
// parallelize by src0 rows
|
|
const uint32_t dr = ct->src0_nrows_per_thread;
|
|
const uint32_t ir0 = dr * ith;
|
|
const uint32_t ir1 = (ir0 + dr) < nr ? (ir0 + dr) : nr;
|
|
|
|
// copy by rows
|
|
for (uint32_t i03 = 0; i03 < ne03; i03++) {
|
|
for (uint32_t i02 = 0; i02 < ne02; i02++) {
|
|
#pragma unroll(2)
|
|
for (uint32_t i01 = ir0; i01 < ir1; i01++) {
|
|
uint8_t* dst_ptr = (uint8_t*) dst->data + i01*nb1 + i02*nb2 + i03*nb3;
|
|
uint8_t* src0_ptr = (uint8_t*) src0->data + i01*nb01 + i02*nb02 + i03*nb03;
|
|
hex_l2fetch(src0_ptr, ne00 * sizeof(float), nb01, 2);
|
|
hvx_copy_f16_f32_uu(dst_ptr, src0_ptr, ne00);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cpy_thread_f32_f16_sameshape(struct htp_copy_context * ct, struct htp_ops_context * octx, const int nth, const int ith) {
|
|
cpy_preamble;
|
|
|
|
// parallelize by src0 rows
|
|
const uint32_t dr = ct->src0_nrows_per_thread;
|
|
const uint32_t ir0 = dr * ith;
|
|
const uint32_t ir1 = (ir0 + dr) < nr ? (ir0 + dr) : nr;
|
|
|
|
// copy by rows
|
|
for (uint32_t i03 = 0; i03 < ne03; i03++) {
|
|
for (uint32_t i02 = 0; i02 < ne02; i02++) {
|
|
#pragma unroll(2)
|
|
for (uint32_t i01 = ir0; i01 < ir1; i01++) {
|
|
uint8_t* dst_ptr = (uint8_t*) dst->data + i01*nb1 + i02*nb2 + i03*nb3;
|
|
uint8_t* src0_ptr = (uint8_t*) src0->data + i01*nb01 + i02*nb02 + i03*nb03;
|
|
hex_l2fetch(src0_ptr, ne00 * sizeof(__fp16), nb01, 2);
|
|
hvx_copy_f32_f16_uu(dst_ptr, src0_ptr, ne00);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cpy_work_func(unsigned int n, unsigned int i, void *data) {
|
|
struct htp_copy_context *ct = (struct htp_copy_context *) data;
|
|
ct->copy(ct, ct->octx, n, i);
|
|
}
|
|
|
|
int op_cpy(struct htp_ops_context * octx) {
|
|
cpy_preamble;
|
|
|
|
const uint32_t n_threads = MIN(nr, octx->n_threads);
|
|
|
|
struct htp_copy_context ct;
|
|
ct.octx = octx;
|
|
|
|
switch (src0->type) {
|
|
case HTP_TYPE_F32: ct.src0_type_size = 4; ct.src0_block_size = 1; ct.src0_blocks_per_row = ne00 / 1; break;
|
|
case HTP_TYPE_F16: ct.src0_type_size = 2; ct.src0_block_size = 1; ct.src0_blocks_per_row = ne00 / 1; break;
|
|
default:
|
|
return HTP_STATUS_NO_SUPPORT;
|
|
}
|
|
|
|
switch (dst->type) {
|
|
case HTP_TYPE_F32: ct.dst_type_size = 4; ct.dst_block_size = 1; ct.dst_blocks_per_row = ne0 / 1; break;
|
|
case HTP_TYPE_F16: ct.dst_type_size = 2; ct.dst_block_size = 1; ct.dst_blocks_per_row = ne0 / 1; break;
|
|
default:
|
|
return HTP_STATUS_NO_SUPPORT;
|
|
}
|
|
|
|
if (octx->flags & HTP_OPFLAGS_SKIP_COMPUTE) {
|
|
return HTP_STATUS_OK;
|
|
}
|
|
|
|
const bool sametype = (src0->type == dst->type);
|
|
const bool transposed = (nb00 > nb01) || (nb0 > nb1);
|
|
const bool sameshape = !transposed && (ne00 == ne0 && ne01 == ne1 && ne02 == ne2 && ne03 == ne3);
|
|
|
|
ct.src0_nrows_per_thread = (nr + n_threads - 1) / n_threads;
|
|
|
|
if (sametype && sameshape) {
|
|
ct.copy = cpy_thread_sametype_sameshape;
|
|
} else if (sameshape) {
|
|
/**/ if (dst->type == HTP_TYPE_F16 && src0->type == HTP_TYPE_F32)
|
|
ct.copy = cpy_thread_f16_f32_sameshape;
|
|
else if (dst->type == HTP_TYPE_F32 && src0->type == HTP_TYPE_F16)
|
|
ct.copy = cpy_thread_f32_f16_sameshape;
|
|
else
|
|
return HTP_STATUS_NO_SUPPORT;
|
|
} else if (sametype) {
|
|
ct.copy = cpy_thread_sametype_reshape;
|
|
} else {
|
|
return HTP_STATUS_NO_SUPPORT;
|
|
}
|
|
|
|
worker_pool_run_func(octx->ctx->worker_pool, cpy_work_func, &ct, n_threads);
|
|
|
|
return HTP_STATUS_OK;
|
|
}
|