← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ck_threadpool.c
Go to the documentation of this file.
1#ifndef _GNU_SOURCE
2#define _GNU_SOURCE
3#endif
4
5/**
6 * @file ck_threadpool.c
7 * @brief Persistent pthread thread pool for CK-Engine inference
8 *
9 * Architecture:
10 * - N-1 worker pthreads created at startup, calling thread executes slot 0
11 * - Workers spin on atomic dispatch counter waiting for work
12 * - Barriers use atomic counter + spin-wait with _mm_pause()
13 * - Hybrid polling: spin CK_THREADPOOL_SPIN_COUNT rounds, then condvar
14 * - All atomics on separate cache lines to avoid false sharing
15 *
16 * Based on the ggml_threadpool design from llama.cpp, adapted for
17 * CK-Engine's kernel dispatch model.
18 */
19
20#include "ck_threadpool.h"
21
22#include <stdlib.h>
23#include <string.h>
24#include <stdio.h>
25#include <errno.h>
26#include <unistd.h>
27#include <time.h>
28#ifdef __linux__
29#include <sched.h>
30#endif
31
32#ifdef __x86_64__
33#include <immintrin.h>
34#define CK_SPIN_PAUSE() _mm_pause()
35#else
36#define CK_SPIN_PAUSE() ((void)0)
37#endif
38
40
41int ck_set_gemm_schedule(int policy)
42{
43 if (policy < CK_GEMM_SCHEDULE_AUTO || policy > CK_GEMM_SCHEDULE_DYNAMIC) {
44 return -1;
45 }
46 atomic_store_explicit(&g_gemm_schedule, policy, memory_order_release);
47 return 0;
48}
49
51{
52 return atomic_load_explicit(&g_gemm_schedule, memory_order_acquire);
53}
54
56{
57 const int policy = ck_get_gemm_schedule();
58 return policy == CK_GEMM_SCHEDULE_AUTO || policy == CK_GEMM_SCHEDULE_DYNAMIC;
59}
60
61/* ============================================================================
62 * Internal Structures (cache-line aligned)
63 * ============================================================================ */
64
65/** Per-worker state */
66typedef struct {
67 pthread_t thread;
68 int id; /* 0 = main, 1..n-1 = workers */
69 struct ck_threadpool *pool;
70} ck_worker_t;
71
72/** Barrier state — all fields on separate cache lines */
73typedef struct {
74 _Alignas(CK_CACHE_LINE) atomic_int n_arrived;
75 _Alignas(CK_CACHE_LINE) atomic_int n_phase;
76 int n_threads;
77 char _pad[CK_CACHE_LINE - sizeof(int)];
78} ck_barrier_t;
79
80/** Thread pool (opaque) */
81struct ck_threadpool {
82 /* Dispatch state — cache-line aligned */
83 _Alignas(CK_CACHE_LINE) atomic_int n_dispatch; /* bumped to wake workers */
84 _Alignas(CK_CACHE_LINE) atomic_int n_complete; /* workers signal completion */
85 _Alignas(CK_CACHE_LINE) atomic_int active_threads; /* active threads for current dispatch */
86 _Alignas(CK_CACHE_LINE) ck_work_fn_t work_fn; /* current work function */
87 void *work_args; /* current work arguments */
88
89 /* Barrier for intra-dispatch synchronization */
90 ck_barrier_t barrier;
91
92 /* Worker management */
93 int n_threads; /* worker capacity (including main) */
94 int default_threads; /* ordinary dispatch width */
95 ck_worker_t workers[CK_THREADPOOL_MAX_THREADS];
96
97 /* Shutdown / pause signals */
98 _Alignas(CK_CACHE_LINE) atomic_int stop;
99 _Alignas(CK_CACHE_LINE) atomic_int paused;
100
101 /* Condvar for sleep/wake (hybrid polling) */
102 pthread_mutex_t dispatch_mutex; /* one multi-worker descriptor at a time */
103 pthread_mutex_t mutex;
104 pthread_cond_t cond_dispatch; /* workers wait here when sleeping */
105 pthread_cond_t cond_done; /* main waits here for completion */
106
107 _Alignas(CK_CACHE_LINE) atomic_int profile_enabled;
108 atomic_uint_fast64_t profile_dispatch_count;
109 atomic_uint_fast64_t profile_dispatch_total_ns;
110 atomic_uint_fast64_t profile_main_work_ns;
111 atomic_uint_fast64_t profile_completion_wait_ns;
112};
113
114/* A caller can be slot 0 even when it did not create the pool. The callback's
115 * dispatch width also cannot be inferred from the pool's worker capacity:
116 * single-worker calls may overlap a multi-worker dispatch. */
117typedef struct {
118 const ck_threadpool_t *pool;
119 int ith;
120 int nth;
121} ck_dispatch_context_t;
122
123static _Thread_local ck_dispatch_context_t g_dispatch_context;
124
125static void run_work(ck_threadpool_t *pool, ck_work_fn_t fn,
126 int ith, int nth, void *args)
127{
128 const ck_dispatch_context_t previous = g_dispatch_context;
129 g_dispatch_context = (ck_dispatch_context_t){pool, ith, nth};
130 fn(ith, nth, args);
131 g_dispatch_context = previous;
132}
133
134static uint64_t monotonic_ns(void)
135{
136 struct timespec now;
137 clock_gettime(CLOCK_MONOTONIC, &now);
138 return (uint64_t)now.tv_sec * UINT64_C(1000000000) + (uint64_t)now.tv_nsec;
139}
140
141/* ============================================================================
142 * Barrier Implementation
143 * ============================================================================ */
144
145static void barrier_init(ck_barrier_t *b, int n_threads)
146{
147 atomic_store(&b->n_arrived, 0);
148 atomic_store(&b->n_phase, 0);
149 b->n_threads = n_threads;
150}
151
152/**
153 * Spin-wait barrier. All threads must call this.
154 * Uses phase counter to allow re-use without reset.
155 */
156static void barrier_wait(ck_barrier_t *b)
157{
158 const int n = b->n_threads;
159 const int phase = atomic_load_explicit(&b->n_phase, memory_order_relaxed);
160
161 if (atomic_fetch_add_explicit(&b->n_arrived, 1, memory_order_acq_rel) == n - 1) {
162 /* Last thread to arrive — reset and advance phase */
163 atomic_store_explicit(&b->n_arrived, 0, memory_order_relaxed);
164 atomic_store_explicit(&b->n_phase, phase + 1, memory_order_release);
165 } else {
166 /* Spin until phase advances */
167 int spins = 0;
168 while (atomic_load_explicit(&b->n_phase, memory_order_acquire) == phase) {
170 spins++;
171 /* After many spins, yield to avoid wasting CPU on oversubscribed systems */
172 if (spins > CK_THREADPOOL_SPIN_COUNT * 16) {
173 sched_yield();
174 spins = 0;
175 }
176 }
177 }
178}
179
180/* ============================================================================
181 * Worker Thread
182 * ============================================================================ */
183
184static void *worker_main(void *arg)
185{
186 ck_worker_t *w = (ck_worker_t *)arg;
187 ck_threadpool_t *pool = w->pool;
188 const int ith = w->id;
189 int last_dispatch = 0;
190
191 for (;;) {
192 /* Spin-wait for new dispatch */
193 int spins = 0;
194 int active = 0;
195 ck_work_fn_t fn = NULL;
196 void *args = NULL;
197 for (;;) {
198 /* Check shutdown */
199 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
200 return NULL;
201 }
202
203 /* Check for new work */
204 int current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
205 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
206 if (current != last_dispatch) {
207 /* Snapshot the epoch and descriptor together. An inactive
208 * worker may still be catching up with an older dispatch. */
209 pthread_mutex_lock(&pool->mutex);
210 current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
211 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
212 if (current != last_dispatch) {
213 last_dispatch = current;
214 if (ith < active) {
215 fn = pool->work_fn;
216 args = pool->work_args;
217 pthread_mutex_unlock(&pool->mutex);
218 break;
219 }
220 }
221 pthread_mutex_unlock(&pool->mutex);
222 spins = 0;
223 }
224
225 /* Threads outside the active subset sleep instead of spinning. */
226 if (ith >= active || spins >= CK_THREADPOOL_SPIN_COUNT) {
227 pthread_mutex_lock(&pool->mutex);
228 for (;;) {
229 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
230 pthread_mutex_unlock(&pool->mutex);
231 return NULL;
232 }
233 current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
234 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
235 if (current != last_dispatch) {
236 last_dispatch = current;
237 if (ith < active) {
238 fn = pool->work_fn;
239 args = pool->work_args;
240 pthread_mutex_unlock(&pool->mutex);
241 goto worker_have_work;
242 }
243 }
244 pthread_cond_wait(&pool->cond_dispatch, &pool->mutex);
245 }
246 }
247
249 spins++;
250 }
251
252worker_have_work:
253 /* Execute work */
254 if (fn) {
255 run_work(pool, fn, ith, active, args);
256 }
257
258 /* Signal completion */
259 if (atomic_fetch_add_explicit(&pool->n_complete, 1, memory_order_acq_rel)
260 == active - 2) {
261 /* Last worker done — wake the calling thread if it is waiting */
262 pthread_mutex_lock(&pool->mutex);
263 pthread_cond_signal(&pool->cond_done);
264 pthread_mutex_unlock(&pool->mutex);
265 }
266 }
267
268 return NULL;
269}
270
271/* ============================================================================
272 * Lifecycle
273 * ============================================================================ */
274
275extern int ck_get_physical_cores(void);
276
277int ck_threadpool_bounded_capacity(int default_threads, int logical_threads)
278{
279 if (default_threads < 1) default_threads = 1;
280 if (default_threads > CK_THREADPOOL_MAX_THREADS) {
281 default_threads = CK_THREADPOOL_MAX_THREADS;
282 }
283 if (logical_threads <= default_threads) return default_threads;
284
285 int capacity = default_threads + (logical_threads - default_threads) / 2;
286 if (capacity > CK_THREADPOOL_MAX_THREADS) {
287 capacity = CK_THREADPOOL_MAX_THREADS;
288 }
289 return capacity;
290}
291
292ck_threadpool_t *ck_threadpool_create_capacity(int default_threads,
293 int capacity_threads)
294{
295 if (default_threads <= 0) {
296 default_threads = ck_get_physical_cores();
297 if (default_threads <= 0) default_threads = 1;
298 /* Cap at reasonable default for memory-bound workloads */
299 if (default_threads > 8) default_threads = 8;
300 }
301 if (capacity_threads < default_threads) {
302 capacity_threads = default_threads;
303 }
304 if (capacity_threads > CK_THREADPOOL_MAX_THREADS) {
305 capacity_threads = CK_THREADPOOL_MAX_THREADS;
306 }
307 if (default_threads > capacity_threads) {
308 default_threads = capacity_threads;
309 }
310
311 ck_threadpool_t *pool = aligned_alloc(CK_CACHE_LINE, sizeof(ck_threadpool_t));
312 if (!pool) return NULL;
313 memset(pool, 0, sizeof(*pool));
314
315 pool->n_threads = capacity_threads;
316 pool->default_threads = default_threads;
317 atomic_store(&pool->n_dispatch, 0);
318 atomic_store(&pool->n_complete, 0);
319 atomic_store(&pool->active_threads, default_threads);
320 atomic_store(&pool->stop, 0);
321 atomic_store(&pool->paused, 0);
322 atomic_store(&pool->profile_enabled, 0);
323 pool->work_fn = NULL;
324 pool->work_args = NULL;
325
326 barrier_init(&pool->barrier, default_threads);
327
328 pthread_mutex_init(&pool->dispatch_mutex, NULL);
329 pthread_mutex_init(&pool->mutex, NULL);
330 pthread_cond_init(&pool->cond_dispatch, NULL);
331 pthread_cond_init(&pool->cond_done, NULL);
332
333 /* Slot 0 runs on the dispatch caller (no worker pthread created). */
334 pool->workers[0].id = 0;
335 pool->workers[0].pool = pool;
336 pool->workers[0].thread = pthread_self();
337
338 /* Spawn N-1 worker threads */
339 for (int i = 1; i < capacity_threads; i++) {
340 pool->workers[i].id = i;
341 pool->workers[i].pool = pool;
342
343 int rc = pthread_create(&pool->workers[i].thread, NULL,
344 worker_main, &pool->workers[i]);
345 if (rc != 0) {
346 fprintf(stderr, "[CK threadpool] Failed to create worker %d: %s\n",
347 i, strerror(rc));
348 /* Reduce thread count to what we managed to create */
349 pool->n_threads = i;
350 barrier_init(&pool->barrier, i);
351 break;
352 }
353 }
354
355 if (pool->n_threads > 1) {
356 fprintf(stderr,
357 "[CK threadpool] Created %d threads (default=%d, 1 main + %d workers)\n",
358 pool->n_threads, pool->default_threads, pool->n_threads - 1);
359 }
360
361 return pool;
362}
363
364ck_threadpool_t *ck_threadpool_create(int n_threads)
365{
366 return ck_threadpool_create_capacity(n_threads, n_threads);
367}
368
369void ck_threadpool_destroy(ck_threadpool_t *pool)
370{
371 if (!pool) return;
372
373 /* Signal shutdown */
374 atomic_store_explicit(&pool->stop, 1, memory_order_release);
375
376 /* Wake all sleeping workers */
377 pthread_mutex_lock(&pool->mutex);
378 pthread_cond_broadcast(&pool->cond_dispatch);
379 pthread_mutex_unlock(&pool->mutex);
380
381 /* Join all worker threads */
382 for (int i = 1; i < pool->n_threads; i++) {
383 pthread_join(pool->workers[i].thread, NULL);
384 }
385
386 pthread_cond_destroy(&pool->cond_dispatch);
387 pthread_cond_destroy(&pool->cond_done);
388 pthread_mutex_destroy(&pool->mutex);
389 pthread_mutex_destroy(&pool->dispatch_mutex);
390
391 free(pool);
392}
393
394/* ============================================================================
395 * Dispatch & Synchronization
396 * ============================================================================ */
397
398void ck_threadpool_dispatch_n(ck_threadpool_t *pool, int active_threads, ck_work_fn_t fn, void *args)
399{
400 if (!pool || !fn) return;
401 if (active_threads <= 0) {
402 active_threads = 1;
403 }
404 if (active_threads > pool->n_threads) {
405 active_threads = pool->n_threads;
406 }
407
408 const int profile = atomic_load_explicit(
409 &pool->profile_enabled, memory_order_relaxed);
410 const uint64_t dispatch_start = profile ? monotonic_ns() : 0;
411
412 /* Single-thread fast path: just call directly */
413 if (active_threads == 1 || pool->n_threads == 1) {
414 run_work(pool, fn, 0, 1, args);
415 if (profile) {
416 const uint64_t dispatch_end = monotonic_ns();
417 atomic_fetch_add_explicit(&pool->profile_dispatch_count, 1, memory_order_relaxed);
418 atomic_fetch_add_explicit(
419 &pool->profile_dispatch_total_ns,
420 dispatch_end - dispatch_start,
421 memory_order_relaxed);
422 atomic_fetch_add_explicit(
423 &pool->profile_main_work_ns,
424 dispatch_end - dispatch_start,
425 memory_order_relaxed);
426 }
427 return;
428 }
429
430 /* The work descriptor and completion counter belong to one dispatch.
431 * Concurrent callers may share a pool, but their multi-worker dispatches
432 * must not overwrite one another's descriptor or barrier state. */
433 pthread_mutex_lock(&pool->dispatch_mutex);
434
435 /* Reset barrier phase for this dispatch */
436 barrier_init(&pool->barrier, active_threads);
437
438 /* Set work descriptor */
439 pthread_mutex_lock(&pool->mutex);
440 pool->work_fn = fn;
441 pool->work_args = args;
442 atomic_store_explicit(&pool->active_threads, active_threads, memory_order_release);
443 atomic_store_explicit(&pool->n_complete, 0, memory_order_release);
444
445 /* Wake workers by bumping dispatch counter */
446 atomic_fetch_add_explicit(&pool->n_dispatch, 1, memory_order_release);
447
448 /* Also signal condvar for sleeping workers */
449 pthread_cond_broadcast(&pool->cond_dispatch);
450 pthread_mutex_unlock(&pool->mutex);
451
452 /* Main thread (ith=0) does its share */
453 const uint64_t main_start = profile ? monotonic_ns() : 0;
454 run_work(pool, fn, 0, active_threads, args);
455 const uint64_t main_end = profile ? monotonic_ns() : 0;
456
457 /* Wait for all workers to complete */
458 if (active_threads > 1) {
459 int spins = 0;
460 while (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
461 < active_threads - 1) {
463 spins++;
464 if (spins >= CK_THREADPOOL_SPIN_COUNT) {
465 pthread_mutex_lock(&pool->mutex);
466 if (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
467 < active_threads - 1) {
468 pthread_cond_wait(&pool->cond_done, &pool->mutex);
469 }
470 pthread_mutex_unlock(&pool->mutex);
471 spins = 0;
472 }
473 }
474 }
475 if (profile) {
476 const uint64_t dispatch_end = monotonic_ns();
477 atomic_fetch_add_explicit(&pool->profile_dispatch_count, 1, memory_order_relaxed);
478 atomic_fetch_add_explicit(
479 &pool->profile_dispatch_total_ns,
480 dispatch_end - dispatch_start,
481 memory_order_relaxed);
482 atomic_fetch_add_explicit(
483 &pool->profile_main_work_ns,
484 main_end - main_start,
485 memory_order_relaxed);
486 atomic_fetch_add_explicit(
487 &pool->profile_completion_wait_ns,
488 dispatch_end - main_end,
489 memory_order_relaxed);
490 }
491 pthread_mutex_unlock(&pool->dispatch_mutex);
492}
493
494void ck_threadpool_dispatch(ck_threadpool_t *pool, ck_work_fn_t fn, void *args)
495{
496 if (!pool) return;
497 ck_threadpool_dispatch_n(pool, pool->default_threads, fn, args);
498}
499
500typedef struct {
501 _Alignas(CK_CACHE_LINE) atomic_int next;
502 int end;
503 int grain_size;
504 ck_range_fn_t fn;
505 void *args;
506} ck_parallel_for_work_t;
507
508static void ck_parallel_for_worker(int ith, int nth, void *opaque)
509{
510 (void)ith;
511 (void)nth;
512 ck_parallel_for_work_t *work = (ck_parallel_for_work_t *)opaque;
513 for (;;) {
514 const int begin = atomic_fetch_add_explicit(
515 &work->next, work->grain_size, memory_order_relaxed);
516 if (begin >= work->end) break;
517 int end = begin + work->grain_size;
518 if (end > work->end) end = work->end;
519 work->fn(begin, end, work->args);
520 }
521}
522
523void ck_threadpool_parallel_for_n(ck_threadpool_t *pool,
524 int active_threads,
525 int begin,
526 int end,
527 int grain_size,
528 ck_range_fn_t fn,
529 void *args)
530{
531 if (!fn || begin >= end) return;
532 if (grain_size <= 0) grain_size = 1;
533 if (!pool || active_threads <= 1) {
534 fn(begin, end, args);
535 return;
536 }
537
538 ck_parallel_for_work_t work = {
539 .end = end,
540 .grain_size = grain_size,
541 .fn = fn,
542 .args = args,
543 };
544 atomic_init(&work.next, begin);
546 pool, active_threads, ck_parallel_for_worker, &work);
547}
548
549void ck_threadpool_barrier(ck_threadpool_t *pool)
550{
551 /* Only participants in this multi-worker dispatch use its barrier.
552 * In particular, an overlapping single-worker callback must never touch
553 * the shared barrier belonging to a different dispatch. */
554 if (!pool || g_dispatch_context.pool != pool ||
555 g_dispatch_context.nth <= 1) return;
556 barrier_wait(&pool->barrier);
557}
558
559/* ============================================================================
560 * Power Management
561 * ============================================================================ */
562
563void ck_threadpool_pause(ck_threadpool_t *pool)
564{
565 if (!pool) return;
566 atomic_store_explicit(&pool->paused, 1, memory_order_release);
567}
568
569void ck_threadpool_resume(ck_threadpool_t *pool)
570{
571 if (!pool) return;
572 atomic_store_explicit(&pool->paused, 0, memory_order_release);
573
574 /* Wake sleeping workers */
575 pthread_mutex_lock(&pool->mutex);
576 pthread_cond_broadcast(&pool->cond_dispatch);
577 pthread_mutex_unlock(&pool->mutex);
578}
579
580/* ============================================================================
581 * Queries
582 * ============================================================================ */
583
584int ck_threadpool_n_threads(const ck_threadpool_t *pool)
585{
586 return pool ? pool->default_threads : 1;
587}
588
589int ck_threadpool_capacity(const ck_threadpool_t *pool)
590{
591 return pool ? pool->n_threads : 1;
592}
593
594int ck_threadpool_thread_id(const ck_threadpool_t *pool)
595{
596 if (!pool) return -1;
597 if (g_dispatch_context.pool == pool) return g_dispatch_context.ith;
598 pthread_t self = pthread_self();
599 for (int i = 0; i < pool->n_threads; i++) {
600 if (pthread_equal(self, pool->workers[i].thread)) {
601 return i;
602 }
603 }
604 return -1;
605}
606
607void ck_threadpool_profile_reset(ck_threadpool_t *pool)
608{
609 if (!pool) return;
610 atomic_store_explicit(&pool->profile_dispatch_count, 0, memory_order_relaxed);
611 atomic_store_explicit(&pool->profile_dispatch_total_ns, 0, memory_order_relaxed);
612 atomic_store_explicit(&pool->profile_main_work_ns, 0, memory_order_relaxed);
613 atomic_store_explicit(&pool->profile_completion_wait_ns, 0, memory_order_relaxed);
614 atomic_store_explicit(&pool->profile_enabled, 1, memory_order_release);
615}
616
618 const ck_threadpool_t *pool, ck_threadpool_profile_t *profile)
619{
620 if (!profile) return;
621 memset(profile, 0, sizeof(*profile));
622 if (!pool) return;
623 profile->dispatch_count = atomic_load_explicit(
624 &pool->profile_dispatch_count, memory_order_relaxed);
625 profile->dispatch_total_ns = atomic_load_explicit(
626 &pool->profile_dispatch_total_ns, memory_order_relaxed);
627 profile->main_work_ns = atomic_load_explicit(
628 &pool->profile_main_work_ns, memory_order_relaxed);
629 profile->completion_wait_ns = atomic_load_explicit(
630 &pool->profile_completion_wait_ns, memory_order_relaxed);
631}
632
633/* ============================================================================
634 * Global Thread Pool
635 * ============================================================================ */
636
637static ck_threadpool_t *g_threadpool = NULL;
638static pthread_once_t g_threadpool_once = PTHREAD_ONCE_INIT;
639
640extern int ck_get_num_threads(void);
641
643{
644#ifdef __linux__
645 cpu_set_t allowed;
646 if (sched_getaffinity(0, sizeof(allowed), &allowed) == 0) {
647 int count = 0;
648 for (int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
649 if (CPU_ISSET(cpu, &allowed)) ++count;
650 }
651 if (count > 0) return count;
652 }
653#endif
654 const long online = sysconf(_SC_NPROCESSORS_ONLN);
655 return online > 0 ? (int)online : 1;
656}
657
658static void global_pool_init(void)
659{
660 const int available_threads = ck_available_logical_cpus();
661 int physical_threads = ck_get_physical_cores();
662 if (physical_threads > available_threads) physical_threads = available_threads;
663 int default_threads = ck_get_num_threads();
664 if (default_threads > available_threads) {
665 default_threads = available_threads;
666 }
667 int capacity_threads = default_threads;
668 const char *capacity_env = getenv("CK_THREADPOOL_CAPACITY");
669 if (capacity_env && atoi(capacity_env) > 0) {
670 capacity_threads = atoi(capacity_env);
671 } else if (!getenv("CK_NUM_THREADS") &&
672 default_threads == physical_threads) {
673 /* Generated runtimes set OMP_NUM_THREADS=1 to keep OpenMP dormant and
674 * configure the CK pool separately. Do not mistake that isolation
675 * setting for a CK capacity cap. A non-physical default remains an
676 * explicit width and does not gain automatic SMT workers. */
677 capacity_threads = ck_threadpool_bounded_capacity(
678 default_threads, available_threads);
679 }
680 if (capacity_threads > available_threads) capacity_threads = available_threads;
682 default_threads, capacity_threads);
683}
684
685ck_threadpool_t *ck_threadpool_global(void)
686{
687 pthread_once(&g_threadpool_once, global_pool_init);
688 return g_threadpool;
689}
690
692{
693 if (g_threadpool) {
695 g_threadpool = NULL;
696 /* Reset once control so pool can be re-created if needed */
697 g_threadpool_once = PTHREAD_ONCE_INIT;
698 }
699}
void ck_threadpool_pause(ck_threadpool_t *pool)
int ck_threadpool_capacity(const ck_threadpool_t *pool)
static void barrier_init(ck_barrier_t *b, int n_threads)
static void barrier_wait(ck_barrier_t *b)
void ck_threadpool_parallel_for_n(ck_threadpool_t *pool, int active_threads, int begin, int end, int grain_size, ck_range_fn_t fn, void *args)
void ck_threadpool_profile_reset(ck_threadpool_t *pool)
void ck_threadpool_resume(ck_threadpool_t *pool)
static int ck_available_logical_cpus(void)
int ck_set_gemm_schedule(int policy)
static pthread_once_t g_threadpool_once
void ck_threadpool_dispatch_n(ck_threadpool_t *pool, int active_threads, ck_work_fn_t fn, void *args)
ck_threadpool_t * ck_threadpool_global(void)
void ck_threadpool_global_destroy(void)
static void global_pool_init(void)
void ck_threadpool_destroy(ck_threadpool_t *pool)
void ck_threadpool_barrier(ck_threadpool_t *pool)
void ck_threadpool_profile_snapshot(const ck_threadpool_t *pool, ck_threadpool_profile_t *profile)
int ck_get_physical_cores(void)
static void ck_parallel_for_worker(int ith, int nth, void *opaque)
static ck_threadpool_t * g_threadpool
ck_threadpool_t * ck_threadpool_create(int n_threads)
ck_threadpool_t * ck_threadpool_create_capacity(int default_threads, int capacity_threads)
static uint64_t monotonic_ns(void)
int ck_gemm_dynamic_schedule_enabled(void)
static void * worker_main(void *arg)
void ck_threadpool_dispatch(ck_threadpool_t *pool, ck_work_fn_t fn, void *args)
int ck_threadpool_bounded_capacity(int default_threads, int logical_threads)
int ck_threadpool_thread_id(const ck_threadpool_t *pool)
static _Thread_local ck_dispatch_context_t g_dispatch_context
#define CK_SPIN_PAUSE()
static void run_work(ck_threadpool_t *pool, ck_work_fn_t fn, int ith, int nth, void *args)
int ck_get_gemm_schedule(void)
static atomic_int g_gemm_schedule
int ck_get_num_threads(void)
int ck_threadpool_n_threads(const ck_threadpool_t *pool)
Persistent pthread thread pool for CK-Engine inference.
@ CK_GEMM_SCHEDULE_AUTO
@ CK_GEMM_SCHEDULE_DYNAMIC
#define CK_THREADPOOL_MAX_THREADS
#define CK_CACHE_LINE
#define CK_THREADPOOL_SPIN_COUNT
void(* ck_range_fn_t)(int begin, int end, void *args)
void(* ck_work_fn_t)(int ith, int nth, void *args)
int32_t id
Definition tokenizer.h:316
const uint8_t size_t count
Definition true_bpe.h:209
uint32_t end
Definition utf8.c:215