* hex-mm: new weight layout and fusion updates * hvx-mm: unroll the new tiled vec_dots to optimize hvx register util * hex-mm: optimize dyn.quant format for q8_0 and q8_1 to reduce overhead in vec_dots. * hvx-mm: parallel quantizer per block for large rows * hvx-mm: simplify and futher optimize dyn.quant and vec_dots * hvx-mm: keep intermediate per tile accumulators in fp16 * hmx-mm: optimize weight dequant by aligning the repacked tiles with the DMA * hmx-mm: remove qweight scratch and just use vtcm_weight * hmx-mm: remove all unused and obsolete code * hmx-mm: the new tiled repack format is here to stay -- rename all x4x2 to _tiled * hmx-mm: improve activation processing with dma prefetch * hex-mm: fix hmx/hvx fallback logic and MUL_MAT_ID allocation (unbreaks OLMoE) * hex-mm: align the weight tiles with dma just like we did in hmx-mm * hex-mm: factor out common mm bits into htp/matmul-ops.h * hex-mm: start moving mm kernel selection to the host * hex-mm: move all of the matmul param compute into the host * hmx-mm: restore pipelined mode * hmx-mm: unroll the dequant functions to optimize register usage * hmx-mm: further improve activation process * hex-mm: use vtcm_seq_alloc for all vtcm allocations and define more common functions * hex-mm: improve mm optimizer to acount for number of activation threads * hex-mm: fix matmul-id kernel params selection (unbreaks OLMoE and LFM) * hexagon: remove support for arch < v73 since HMX is now required for most use-cases * hex-mm: cleanup naming for consistency * hex-mm: make sure matmul fusion accounts for vtcm allocation * hex-mm: minor cleanup for kernel_params definition * hex-mm: replace hardcoded limits with proper checks for vtcm requirements * hex-mm: add support for non-tiled mm as a fallback option and factor out hvx kernels into separate header * hex-mm: remove unused functions * hex-mm: add shorthand for MM_SELECT in run-tool script * hvx-mm: factor out hvx/hmx microkernels and unify matmul entry and dispatch * hex-mm: further cleanup matmul fallback path * hex-mm: refactor matmul entry point and dispatch a bit further * hexagon: update cmake build to enable hmx for everything * hex-ops: optimize kernel_param updates and include summary in the logs * hex-mm: add support for GGML_HEXAGON_MM_SELECT * hex-mm: add hex-common header * hex-mm: pass correct number of tasks to workpool * hex-mm: add proper checks for no-work in dyn.quant tasks * hex-mm: convert all quantizers into a macro * hex-mm: fix hvx-flat fallback to pass all MUL_MAT tests * hex-mm: vectorize q8_1 quantizer * hex-mm: improve fused ffn mm stride handling * hex-mm: consistent use of n_threads and pipeline in kernel_params * hexagon: minor formatting * hex-mm: update MUL_MAT_ID kernel_param handling to make sure host/npu are in sync * hvx-mm: go back to accumulating in fp32 in tiled hvx kernels, more accurate and same perf * hvx-mm: unroll the loops and remove masking that is not needed for tiled accums * hmx-mm: optimize activation processing (slit loops, some unrolling, etc) * hmx-mm: minor optimization for output processing * hex-mm: consistent use of uint32_t and size_t in mm kernels * hex-mm: remove legacy restrictions for rows to be multiple of 256 * hexagon: replace sprintf with snprintf * hex-mm: relax hardcoded nrows checks and rely on VTCM size requirements * hexagon: minor alignment fix * hexagon: fix trailing spaces * hex-mm: relax padding from 256 to 128 (leftovers) * hex-mm: remove redundant checks for weight align to 128 we always use 2D dma for the weights and align them properly * hmx-mm: MUL_MAT_ID better work distribution between hvx threads and hmx tracing * hex-mm: specialize per-token mmid activation handling * hex-profile: update python scripts to handle kernel-params section in the logging output * hex-mm: move n_prefetch (aka dma_depth) into kernel params and remove unused fields * hex-trace: use easier to parse format, simply and fix post-proc scripts * hmx-mm: relax 32 row limit for output processing which helps utilization * hmx-mm: use start-chunk idx for tracing info * hmx-mm: parameterize activation dma pipeline * hexagon: add support for simple graph caching to avoid recomputing kernel-params * hex-mm: remove left-over repack functions * hex-mm: tighten n_prefetch asserts * hex-mm: remove duplicate round/align_up helper * hexagon: cleanup common header used in host/npu * hexagon: update early wakeup threshold * hmx-mm: define cost constants and update solver to assume that repacked ne[1] is padded to 32 * hmx-mm: make precompute_matmul a bit more readable (split into smaller functions, etc) * hex-mm: remove n_threads constraint * hex-mm: minor formatting updates * hex-mm: remove obsolete profiling logs * hex-mm: restore hardcode gate to refuse lm-head to avoid repacking that tensor
376 lines
11 KiB
C
376 lines
11 KiB
C
#ifndef HTP_DMA_H
|
|
#define HTP_DMA_H
|
|
|
|
#include <HAP_farf.h>
|
|
#include <hexagon_types.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include "hex-utils.h"
|
|
|
|
#include "hex-profile.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// Define the HW descriptor structs here since the ones in HexSDK are a bit out of date
|
|
typedef struct dma_descriptor_1d_s {
|
|
void * next;
|
|
uint32_t size:24;
|
|
uint32_t desc_size:2;
|
|
uint32_t dst_comp:1;
|
|
uint32_t src_comp:1;
|
|
uint32_t dst_bypass:1;
|
|
uint32_t src_bypass:1;
|
|
uint32_t order:1;
|
|
uint32_t done:1;
|
|
void * src;
|
|
void * dst;
|
|
} dma_descriptor_1d;
|
|
|
|
#if __HVX_ARCH__ < 75
|
|
|
|
typedef struct dma_descriptor_2d_s {
|
|
void * next;
|
|
uint32_t reserved0:24;
|
|
uint32_t desc_size:2;
|
|
uint32_t dst_comp:1;
|
|
uint32_t src_comp:1;
|
|
uint32_t dst_bypass:1;
|
|
uint32_t src_bypass:1;
|
|
uint32_t order:1;
|
|
uint32_t done:1;
|
|
void * src;
|
|
void * dst;
|
|
uint32_t desc_type:8;
|
|
uint32_t reserved1:24;
|
|
uint32_t row_size:16;
|
|
uint32_t nrows:16;
|
|
uint32_t src_stride:16;
|
|
uint32_t dst_stride:16;
|
|
uint32_t src_offset:16;
|
|
uint32_t dst_offset:16;
|
|
} dma_descriptor_2d;
|
|
|
|
#else
|
|
|
|
typedef struct dma_descriptor_2d_s {
|
|
void * next;
|
|
uint32_t dst_stride:24;
|
|
uint32_t desc_size:2;
|
|
uint32_t dst_comp:1;
|
|
uint32_t src_comp:1;
|
|
uint32_t dst_bypass:1;
|
|
uint32_t src_bypass:1;
|
|
uint32_t order:1;
|
|
uint32_t done:1;
|
|
void * src;
|
|
void * dst;
|
|
uint32_t desc_type:8;
|
|
uint32_t reserved0:24;
|
|
uint32_t row_size:24;
|
|
uint32_t nrows_lo:8;
|
|
uint32_t nrows_hi:8;
|
|
uint32_t src_stride:24;
|
|
uint32_t offset:24;
|
|
uint32_t reserved1:8;
|
|
} dma_descriptor_2d;
|
|
|
|
#endif
|
|
|
|
typedef struct {
|
|
void *dst;
|
|
const void *src;
|
|
} dma_ptr;
|
|
|
|
typedef struct {
|
|
dma_descriptor_2d * desc; // descriptor pointers
|
|
dma_descriptor_2d * tail; // tail pointer
|
|
dma_ptr * dptr; // dst/src pointers
|
|
uint32_t push_idx;
|
|
uint32_t pop_idx;
|
|
uint32_t capacity;
|
|
uint32_t idx_mask;
|
|
struct htp_thread_trace * trace;
|
|
} dma_queue;
|
|
|
|
dma_queue * dma_queue_create(size_t capacity);
|
|
void dma_queue_delete(dma_queue * q);
|
|
void dma_queue_flush(dma_queue * q);
|
|
|
|
// TODO: technically we don't need these and could use Q6_dmstart/wait/etc instead
|
|
// but those do not seem to always compiler properly.
|
|
static inline void dmstart(void * next) {
|
|
asm volatile(" release(%0):at" : : "r"(next));
|
|
asm volatile(" dmstart(%0)" : : "r"(next));
|
|
}
|
|
|
|
static inline void dmlink(void * cur, void * next) {
|
|
asm volatile(" release(%0):at" : : "r"(next));
|
|
asm volatile(" dmlink(%0, %1)" : : "r"(cur), "r"(next));
|
|
}
|
|
|
|
static inline unsigned int dmpoll(void) {
|
|
unsigned int ret = 0;
|
|
asm volatile(" %0 = dmpoll" : "=r"(ret) : : "memory");
|
|
return ret;
|
|
}
|
|
|
|
static inline unsigned int dmwait(void) {
|
|
unsigned int ret = 0;
|
|
asm volatile(" %0 = dmwait" : "=r"(ret) : : "memory");
|
|
return ret;
|
|
}
|
|
|
|
static inline dma_ptr dma_make_ptr(void *dst, const void *src)
|
|
{
|
|
dma_ptr p = { dst, src };
|
|
return p;
|
|
}
|
|
|
|
static const uint32_t dma_src_l2_bypass_on = 1;
|
|
static const uint32_t dma_dst_l2_bypass_on = 1;
|
|
|
|
static inline bool dma_queue_push_single_1d(dma_queue * q, dma_ptr dptr, size_t size) {
|
|
if (((q->push_idx + 1) & q->idx_mask) == q->pop_idx) {
|
|
FARF(HIGH, "dma-push: queue full\n");
|
|
return false;
|
|
}
|
|
|
|
dma_descriptor_1d * desc = (dma_descriptor_1d *) &q->desc[q->push_idx];
|
|
desc->next = NULL;
|
|
desc->desc_size = 0; // 1D mode
|
|
desc->src_bypass = dma_src_l2_bypass_on;
|
|
desc->dst_bypass = dma_dst_l2_bypass_on;
|
|
desc->order = 0;
|
|
desc->done = 0;
|
|
desc->src = (void *) dptr.src;
|
|
desc->dst = (void *) dptr.dst;
|
|
desc->size = size;
|
|
|
|
q->dptr[q->push_idx] = dptr;
|
|
|
|
if (size) {
|
|
htp_trace_event_start(q->trace, HTP_TRACE_EVT_DMA, q->push_idx);
|
|
dmlink(q->tail, desc);
|
|
q->tail = (dma_descriptor_2d *) desc;
|
|
} else {
|
|
desc->done = 1;
|
|
}
|
|
|
|
// FARF(ERROR, "dma-push: i %u row-size %u nrows %d dst %p src %p\n", q->push_idx, row_size, nrows, dptr.dst, dptr.src);
|
|
q->push_idx = (q->push_idx + 1) & q->idx_mask;
|
|
return true;
|
|
}
|
|
|
|
static inline bool dma_queue_push_single_2d(dma_queue * q, dma_ptr dptr, size_t dst_stride, size_t src_stride, size_t row_size, size_t nrows) {
|
|
if (((q->push_idx + 1) & q->idx_mask) == q->pop_idx) {
|
|
FARF(HIGH, "dma-push: queue full\n");
|
|
return false;
|
|
}
|
|
|
|
dma_descriptor_2d * desc = &q->desc[q->push_idx];
|
|
|
|
desc->next = NULL;
|
|
desc->reserved0 = 0;
|
|
desc->reserved1 = 0;
|
|
desc->desc_size = 1; // 2d mode
|
|
desc->src_bypass = dma_src_l2_bypass_on;
|
|
desc->dst_bypass = dma_dst_l2_bypass_on;
|
|
desc->src_comp = 0;
|
|
desc->dst_comp = 0;
|
|
desc->order = 0;
|
|
desc->done = 0;
|
|
desc->src_stride = src_stride;
|
|
desc->dst_stride = dst_stride;
|
|
desc->src = (void *) dptr.src;
|
|
desc->dst = (void *) dptr.dst;
|
|
desc->row_size = row_size;
|
|
|
|
#if __HVX_ARCH__ < 75
|
|
desc->desc_type = 0; // 2d (16-bit) mode
|
|
desc->nrows = nrows;
|
|
desc->src_offset = 0;
|
|
desc->dst_offset = 0;
|
|
#else
|
|
desc->desc_type = 9; // 2d (24-bit) mode
|
|
desc->nrows_lo = (nrows & 0xff);
|
|
desc->nrows_hi = (nrows >> 8);
|
|
desc->offset = 0;
|
|
#endif
|
|
|
|
q->dptr[q->push_idx] = dptr;
|
|
|
|
if (nrows) {
|
|
htp_trace_event_start(q->trace, HTP_TRACE_EVT_DMA, q->push_idx);
|
|
dmlink(q->tail, desc);
|
|
q->tail = desc;
|
|
} else {
|
|
desc->done = 1;
|
|
}
|
|
|
|
// FARF(ERROR, "dma-push: i %u row-size %u nrows %d dst %p src %p\n", q->push_idx, row_size, nrows, dptr.dst, dptr.src);
|
|
q->push_idx = (q->push_idx + 1) & q->idx_mask;
|
|
return true;
|
|
}
|
|
|
|
static inline dma_ptr dma_queue_pop(dma_queue * q) {
|
|
dma_ptr dptr = { NULL };
|
|
|
|
if (q->push_idx == q->pop_idx) {
|
|
return dptr;
|
|
}
|
|
|
|
dma_descriptor_2d * desc = &q->desc[q->pop_idx];
|
|
|
|
// Wait for desc to complete
|
|
if (!desc->done) {
|
|
while (!desc->done) {
|
|
dmpoll();
|
|
}
|
|
}
|
|
htp_trace_event_stop(q->trace, HTP_TRACE_EVT_DMA, q->pop_idx);
|
|
|
|
dptr = q->dptr[q->pop_idx];
|
|
|
|
// FARF(ERROR, "dma-pop: i %u dst %p src %p\n", q->pop_idx, dptr.dst, dptr.src);
|
|
q->pop_idx = (q->pop_idx + 1) & q->idx_mask;
|
|
return dptr;
|
|
}
|
|
|
|
static inline dma_ptr dma_queue_pop_nowait(dma_queue * q) {
|
|
dma_ptr dptr = { NULL };
|
|
|
|
if (q->push_idx == q->pop_idx) {
|
|
return dptr;
|
|
}
|
|
|
|
dptr = q->dptr[q->pop_idx];
|
|
|
|
// FARF(ERROR, "dma-pop-nowait: i %u dst %p src %p\n", q->pop_idx, dptr.dst, dptr.src);
|
|
q->pop_idx = (q->pop_idx + 1) & q->idx_mask;
|
|
return dptr;
|
|
}
|
|
|
|
static inline bool dma_queue_empty(dma_queue * q) {
|
|
return q->push_idx == q->pop_idx;
|
|
}
|
|
|
|
static inline uint32_t dma_queue_depth(dma_queue * q) {
|
|
return (q->push_idx - q->pop_idx) & q->idx_mask;
|
|
}
|
|
|
|
static inline uint32_t dma_queue_capacity(dma_queue * q) {
|
|
return q->capacity;
|
|
}
|
|
|
|
#if __HVX_ARCH__ < 75
|
|
|
|
// Overflow-safe DMA push: all 2d descriptor fields (row_size, nrows, src_stride, dst_stride) are 16-bit, max 65535.
|
|
// This version transparently handles values that exceed the 16-bit limit and submits chained DMA transtions.
|
|
|
|
#define DMA_MAX_FIELD_VAL 65535u
|
|
|
|
static inline bool dma_queue_push(dma_queue *q, dma_ptr dptr, size_t dst_stride, size_t src_stride, size_t row_size, size_t nrows) {
|
|
// Fast path: everything fits in 16 bits
|
|
if (nrows == 0 || __builtin_expect(
|
|
row_size <= DMA_MAX_FIELD_VAL &&
|
|
nrows <= DMA_MAX_FIELD_VAL &&
|
|
src_stride <= DMA_MAX_FIELD_VAL &&
|
|
dst_stride <= DMA_MAX_FIELD_VAL, 1)) {
|
|
return dma_queue_push_single_2d(q, dptr, dst_stride, src_stride, row_size, nrows);
|
|
}
|
|
|
|
// Contiguous block
|
|
// Use 1d DMA mode which supports sizes up to 24-bits (16MB)
|
|
if (nrows == 1 || (row_size == src_stride && row_size == dst_stride)) {
|
|
size_t total = row_size * nrows;
|
|
return dma_queue_push_single_1d(q, dptr, total);
|
|
}
|
|
|
|
// Stride overflow — fall back to row-by-row.
|
|
{
|
|
const uint8_t *src = (const uint8_t *) dptr.src;
|
|
uint8_t *dst = (uint8_t *) dptr.dst;
|
|
for (size_t r = 0; r < nrows; ++r) {
|
|
dma_ptr p = dma_make_ptr(dst + r * dst_stride, src + r * src_stride);
|
|
if (!dma_queue_push_single_1d(q, p, row_size))
|
|
return false;
|
|
if (r + 1 < nrows)
|
|
dma_queue_pop(q);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#else // HVX_ARCH >= 75
|
|
|
|
static inline bool dma_queue_push(dma_queue *q, dma_ptr dptr, size_t dst_stride, size_t src_stride, size_t row_size, size_t nrows) {
|
|
// On v75 and up we always use 2d 24-bit mode
|
|
return dma_queue_push_single_2d(q, dptr, dst_stride, src_stride, row_size, nrows);
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline bool dma_queue_push_ddr_to_vtcm(dma_queue * q, dma_ptr dptr, size_t dst_row_size, size_t src_row_size, size_t nrows) {
|
|
return dma_queue_push(q, dptr, dst_row_size, src_row_size, src_row_size, nrows);
|
|
}
|
|
|
|
static inline bool dma_queue_push_vtcm_to_ddr(dma_queue * q, dma_ptr dptr, size_t dst_row_size, size_t src_row_size, size_t nrows) {
|
|
return dma_queue_push(q, dptr, dst_row_size, src_row_size, dst_row_size, nrows);
|
|
}
|
|
|
|
#define DMA_CACHE_MAX_SIZE 64U
|
|
|
|
typedef struct {
|
|
uint8_t *base;
|
|
uint32_t line_size;
|
|
uint32_t capacity;
|
|
uint32_t src[DMA_CACHE_MAX_SIZE];
|
|
uint16_t age[DMA_CACHE_MAX_SIZE];
|
|
} dma_cache;
|
|
|
|
static inline void dma_cache_init(dma_cache *c, uint8_t *base, uint32_t line_size, uint32_t capacity)
|
|
{
|
|
c->capacity = (capacity > DMA_CACHE_MAX_SIZE) ? DMA_CACHE_MAX_SIZE : capacity;
|
|
c->base = base;
|
|
c->line_size = line_size;
|
|
|
|
for (unsigned i=0; i < c->capacity; i++) {
|
|
c->src[i] = 0;
|
|
c->age[i] = 0;
|
|
}
|
|
}
|
|
|
|
static inline bool dma_cache_push(dma_queue *q, dma_cache *c, const uint8_t * src, uint32_t dst_stride, uint32_t src_stride, uint32_t row_size, uint32_t nrows)
|
|
{
|
|
uint32_t o_idx = 0;
|
|
uint16_t o_age = 0;
|
|
uint8_t * dst = 0;
|
|
|
|
for (unsigned i=0; i < c->capacity; i++) {
|
|
if (c->src[i] == (uint32_t) src) {
|
|
c->age[i] = 0;
|
|
dst = c->base + (i * c->line_size); nrows = 0; // dummy dma
|
|
// FARF(ERROR, "dma-cache: found %p", src);
|
|
} else {
|
|
c->age[i]++;
|
|
if (c->age[i] > o_age) { o_age = c->age[i]; o_idx = i; }
|
|
}
|
|
}
|
|
if (!dst) {
|
|
// FARF(ERROR, "dma-cache: replacing #%u : age %u %p -> %p", o_idx, c->age[o_idx], (void *) c->src[o_idx], src);
|
|
c->age[o_idx] = 0;
|
|
c->src[o_idx] = (uint32_t) src;
|
|
dst = c->base + o_idx * c->line_size; // normal nrows dma
|
|
}
|
|
|
|
return dma_queue_push(q, dma_make_ptr(dst, src), dst_stride, src_stride, row_size, nrows);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|
|
|
|
#endif /* HTP_DMA_H */
|