* hex-mm: fix artificial limit in the solver that restricted number of act-prep threads * hex-mm: fix warning * hex-prof: do not apply --top to the timeline report * hmx-mm: add suport for tiled act-processing to better distribute hvx work * hex-l2: add tracing for l2flush events * workqueue: redo the legacy workpool api to match hmx-queue and dma-queue * hmx-mm: fix f32 activation buffer alignmnet for nhvx=5,6,7 * hex-work: minor cleanup for work-queue apis * hex-work: further cleanup of the work-queue api * hex-l2: optimize l2flushes at the opbatch level * hex-work: remove unused mask * hex-work: no need to drop hvx ctx in the work-queue * hex-work: add explicit wakeup/suspend and make threads spin * hex-bufs: mark any non-weight tensor as compute * hex-dma: dma-queue support for alias queues and cached dma * hex-l2: track tensor aliases and delay or skip flushes as much as possible * hex-l2: simplify tensor alias handling * hex-l2: handle overlapping views as a circular list of aliases * hex-tens: add flags helper * hex-l2: add helper for marking tensors clearn/dirty * hex-l2: mark binary and rope outputs as l2-clean and keep the rest as is for now * hex-l2: proper support for handling all tensor overlap scenarios * hex-trace: instrument matmul init code and cleanup trace checks * hex-thread: introduce dedicated main thread with explicit stack and priority * hex-l2: track dirty state as bitmap and introduce threaded flush * hex-trace: remove redundant checks for ctx != null * hex-l2: allocate entire context as one buffer and l2fetch it after big flushes * hex-l2: disable tensor clearing in binary and rope for now seems to cause issues with fusion * hmx-mm: update act proc to use fastdivs and fix DMA overflow * hmx-mm: make MUL_MAT_ID kernels robust to multi-chunk cases (start_row>0) * hex-queue: remove obsolete queue interfaces and flush hmx-queue at the end of the op-batch * hex-queue: dont use early wakeup for small op-batches * hex-tensors: properly cap max_tensors in op-batches and dirty_map * hex-l2: make sure threaded l2flush does proper rounding * hex-l2: factor out htp_tensor_flush for reuse (if needed) * hex-l2: optimize tensor flushes by coalescing flush-all * hex-l2: optimize multi-threaded flush * hex-drv: futureproof version checks * hexagon: fix errors and warnings on windows * hex-main: update main thread to only use dspqueue_read, dspqueue_peek is not available on some platforms * hex-main: add fallback mode for dspqueue with callbacks * hex-main: introduce fallback mode for using dspqueue callbacks for full op processing * hex-main: remove early wakeup, not helping and seems to cause some errors with certain batch sizes * hex-l2: make sure to use invalidate version of flushall * hex-l2: dont try to trace early l2flush at the start of op-batch * hex-main: remove offset_ctx that must be zero anyway * hex-hmx: fix hmx_queue_depth to use idx_write - idx_read * hex-hmx: use atomic_load for idx_read/write * hex-main: add static assert to make sure n_threads are aligned
245 lines
7.8 KiB
C
245 lines
7.8 KiB
C
#include "work-queue.h"
|
|
#include "hex-utils.h"
|
|
|
|
#include <qurt.h>
|
|
#include <qurt_hvx.h>
|
|
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "HAP_farf.h"
|
|
|
|
#define LOWEST_USABLE_QURT_PRIO (254)
|
|
|
|
// internal structure kept in thread-local storage per instance of work queue
|
|
typedef struct {
|
|
work_queue_t queue;
|
|
unsigned int id;
|
|
} worker_context_t;
|
|
|
|
struct work_queue_task_s {
|
|
work_queue_func_t func;
|
|
void * data;
|
|
unsigned int n_threads;
|
|
atomic_uint barrier;
|
|
};
|
|
|
|
// internal structure kept in thread-local storage per instance of work queue
|
|
struct work_queue_s {
|
|
atomic_uint seqn; // seqno used to detect new jobs
|
|
atomic_uint idx_read; // Updated by producer (pop/reclaim)
|
|
unsigned int idx_write; // Updated by producer (push)
|
|
uint32_t idx_mask;
|
|
uint32_t capacity;
|
|
|
|
qurt_thread_t thread[WORK_QUEUE_MAX_N_THREADS]; // thread ID's of the workers
|
|
worker_context_t context[WORK_QUEUE_MAX_N_THREADS]; // worker contexts
|
|
void * stack[WORK_QUEUE_MAX_N_THREADS]; // thread stack pointers
|
|
unsigned int n_threads; // total threads (workers + main)
|
|
unsigned int n_workers; // number of active threads (just workers)
|
|
|
|
atomic_bool active; // workers are polling/active
|
|
atomic_bool killed; // threads need to exit
|
|
bool external_mem; // memory owned externally
|
|
|
|
struct work_queue_task_s queue[] __attribute__((aligned(HEX_L2_LINE_SIZE)));
|
|
};
|
|
|
|
static void work_queue_thread(void * context) {
|
|
worker_context_t * me = (worker_context_t *) context;
|
|
work_queue_t q = me->queue;
|
|
|
|
FARF(HIGH, "work-queue: thread %u started", me->id);
|
|
|
|
unsigned int prev_seqn = 0;
|
|
|
|
while (!atomic_load_explicit(&q->killed, memory_order_relaxed)) {
|
|
unsigned int seqn = atomic_load_explicit(&q->seqn, memory_order_acquire);
|
|
if (seqn == prev_seqn) {
|
|
if (atomic_load_explicit(&q->active, memory_order_relaxed)) {
|
|
hex_pause();
|
|
} else {
|
|
qurt_futex_wait(&q->seqn, prev_seqn);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
prev_seqn = seqn;
|
|
|
|
// Process all active tasks in the queue
|
|
unsigned int ir = atomic_load_explicit(&q->idx_read, memory_order_relaxed);
|
|
unsigned int iw = q->idx_write;
|
|
|
|
while (ir != iw) {
|
|
struct work_queue_task_s * task = &q->queue[ir];
|
|
|
|
unsigned int n = task->n_threads;
|
|
unsigned int i = me->id;
|
|
if (i < n) {
|
|
task->func(n, i, task->data);
|
|
|
|
atomic_fetch_sub_explicit(&task->barrier, 1, memory_order_release);
|
|
} else {
|
|
while (atomic_load_explicit(&task->barrier, memory_order_relaxed) > 0) {
|
|
hex_pause();
|
|
}
|
|
}
|
|
|
|
ir = (ir + 1) & q->idx_mask;
|
|
}
|
|
}
|
|
|
|
FARF(HIGH, "work-queue: thread %u stopped", me->id);
|
|
}
|
|
|
|
bool work_queue_run_async(work_queue_t q, work_queue_func_t func, void * data, unsigned int n) {
|
|
if (n > q->n_threads) {
|
|
FARF(ERROR, "work-queue: invalid number of jobs %u for n-threads %u", n, q->n_threads);
|
|
return false;
|
|
}
|
|
|
|
unsigned int ir = atomic_load_explicit(&q->idx_read, memory_order_relaxed);
|
|
unsigned int iw = q->idx_write;
|
|
|
|
if (((iw + 1) & q->idx_mask) == ir) {
|
|
FARF(ERROR, "work-queue-push: queue is full\n");
|
|
return false;
|
|
}
|
|
|
|
struct work_queue_task_s * task = &q->queue[iw];
|
|
task->func = func;
|
|
task->data = data;
|
|
task->n_threads = n;
|
|
atomic_store_explicit(&task->barrier, n, memory_order_relaxed);
|
|
|
|
q->idx_write = (iw + 1) & q->idx_mask;
|
|
|
|
// publish job to workers (already awake and polling)
|
|
atomic_fetch_add_explicit(&q->seqn, 1, memory_order_release);
|
|
|
|
// main thread runs job #0
|
|
func(n, 0, data);
|
|
|
|
atomic_fetch_sub_explicit(&task->barrier, 1, memory_order_release);
|
|
|
|
while (atomic_load_explicit(&task->barrier, memory_order_relaxed) > 0) {
|
|
hex_pause();
|
|
}
|
|
|
|
atomic_thread_fence(memory_order_acquire);
|
|
|
|
atomic_store_explicit(&q->idx_read, (ir + 1) & q->idx_mask, memory_order_relaxed);
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t work_queue_sizeof(uint32_t n_threads, uint32_t capacity, uint32_t stack_size) {
|
|
capacity = hex_ceil_pow2(capacity);
|
|
uint32_t n_workers = n_threads > 1 ? n_threads - 1 : 0;
|
|
size_t size_stacks = stack_size * n_workers;
|
|
size_t size_q = hex_align_up(sizeof(struct work_queue_s) + capacity * sizeof(struct work_queue_task_s), HEX_L2_LINE_SIZE);
|
|
return size_stacks + size_q;
|
|
}
|
|
|
|
size_t work_queue_alignof(void) {
|
|
return 4096;
|
|
}
|
|
|
|
work_queue_t work_queue_init(void * ptr, uint32_t n_threads, uint32_t capacity, uint32_t stack_size) {
|
|
capacity = hex_ceil_pow2(capacity);
|
|
uint32_t n_workers = n_threads > 1 ? n_threads - 1 : 0;
|
|
unsigned char * mem_blob = (unsigned char *) ptr;
|
|
|
|
work_queue_t q = (work_queue_t) (mem_blob + stack_size * n_workers);
|
|
memset(q, 0, sizeof(struct work_queue_s) + capacity * sizeof(struct work_queue_task_s));
|
|
|
|
q->n_threads = n_threads;
|
|
q->n_workers = n_workers;
|
|
q->external_mem = true;
|
|
q->capacity = capacity;
|
|
|
|
for (unsigned int i = 0; i < n_workers; i++) {
|
|
q->stack[i] = mem_blob; mem_blob += stack_size;
|
|
q->thread[i] = 0;
|
|
q->context[i].id = i + 1;
|
|
q->context[i].queue = q;
|
|
}
|
|
|
|
atomic_init(&q->idx_read, 0);
|
|
atomic_init(&q->seqn, 0);
|
|
atomic_init(&q->active, false);
|
|
q->idx_write = 0;
|
|
q->idx_mask = capacity - 1;
|
|
q->killed = 0;
|
|
for (int i = 0; i < (int) capacity; i++) {
|
|
atomic_init(&q->queue[i].barrier, 0);
|
|
q->queue[i].func = NULL;
|
|
q->queue[i].data = NULL;
|
|
q->queue[i].n_threads = 0;
|
|
}
|
|
|
|
// launch the workers
|
|
qurt_thread_attr_t attr;
|
|
qurt_thread_attr_init(&attr);
|
|
|
|
for (unsigned int i = 0; i < n_workers; i++) {
|
|
qurt_thread_attr_set_stack_addr(&attr, q->stack[i]);
|
|
qurt_thread_attr_set_stack_size(&attr, stack_size);
|
|
|
|
char thread_name[32];
|
|
snprintf(thread_name, sizeof(thread_name), "work-queue:%u", i);
|
|
qurt_thread_attr_set_name(&attr, thread_name);
|
|
|
|
// set up priority - by default, match the creating thread's prio
|
|
int prio = qurt_thread_get_priority(qurt_thread_get_id());
|
|
if (prio < 1) {
|
|
prio = 1;
|
|
}
|
|
if (prio > LOWEST_USABLE_QURT_PRIO) {
|
|
prio = LOWEST_USABLE_QURT_PRIO;
|
|
}
|
|
|
|
qurt_thread_attr_set_priority(&attr, prio);
|
|
|
|
int err = qurt_thread_create(&q->thread[i], &attr, work_queue_thread, (void *) &q->context[i]);
|
|
if (err) {
|
|
FARF(ERROR, "Could not launch worker threads!");
|
|
work_queue_free(q);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
void work_queue_free(work_queue_t q) {
|
|
if (!q) { return; }
|
|
|
|
atomic_store_explicit(&q->killed, 1, memory_order_relaxed);
|
|
atomic_fetch_add_explicit(&q->seqn, 1, memory_order_release);
|
|
qurt_futex_wake(&q->seqn, q->n_workers);
|
|
|
|
for (unsigned int i = 0; i < q->n_workers; i++) {
|
|
if (q->thread[i]) {
|
|
int status;
|
|
(void) qurt_thread_join(q->thread[i], &status);
|
|
}
|
|
}
|
|
}
|
|
|
|
void work_queue_wakeup(work_queue_t q) {
|
|
if (!atomic_load_explicit(&q->active, memory_order_relaxed)) {
|
|
atomic_store_explicit(&q->active, true, memory_order_release);
|
|
// Increment seqn and wake workers to transition them out of sleep
|
|
atomic_fetch_add_explicit(&q->seqn, 1, memory_order_release);
|
|
qurt_futex_wake(&q->seqn, q->n_workers);
|
|
}
|
|
}
|
|
|
|
void work_queue_suspend(work_queue_t q) {
|
|
atomic_store_explicit(&q->active, false, memory_order_release);
|
|
}
|