Skip to content

Commit 9a79dfd

Browse files
Alex7MVclaude
andcommitted
feat: add per-thread timing accumulators to CPU threadpool
Track t_compute_us, t_barrier_us, t_poll_us, t_idle_us, n_compute_ops, n_barrier_ops per worker thread via atomic accumulators. Auto-print on threadpool free. New API: ggml_threadpool_print_timings(), ggml_threadpool_reset_timings(). Barrier signature now takes thread index for timing attribution. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 79da4de commit 9a79dfd

9 files changed

Lines changed: 150 additions & 58 deletions

File tree

ggml/include/ggml-cpu.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ extern "C" {
6060
GGML_BACKEND_API int ggml_threadpool_get_n_threads (struct ggml_threadpool * threadpool);
6161
GGML_BACKEND_API void ggml_threadpool_pause (struct ggml_threadpool * threadpool);
6262
GGML_BACKEND_API void ggml_threadpool_resume (struct ggml_threadpool * threadpool);
63+
GGML_BACKEND_API void ggml_threadpool_print_timings (struct ggml_threadpool * threadpool);
64+
GGML_BACKEND_API void ggml_threadpool_reset_timings (struct ggml_threadpool * threadpool);
6365

6466
// ggml_graph_plan() has to be called before ggml_graph_compute()
6567
// when plan.work_size > 0, caller must allocate memory for plan.work_data

ggml/src/ggml-cpu/amx/mmq.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2431,7 +2431,7 @@ void ggml_backend_amx_mul_mat(const ggml_compute_params * params, struct ggml_te
24312431
});
24322432
// }
24332433

2434-
ggml_barrier(params->threadpool);
2434+
ggml_barrier(params->threadpool, params->ith);
24352435

24362436
if (M == 1) {
24372437
// MB = 1 and handle 8 tiles in each block

ggml/src/ggml-cpu/ggml-cpu-impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ static __m256 __lasx_xvreplfr2vr_s(const float val) {
529529
#endif
530530

531531
// TODO: move to ggml-threading
532-
void ggml_barrier(struct ggml_threadpool * tp);
532+
void ggml_barrier(struct ggml_threadpool * tp, int ith);
533533

534534
void ggml_threadpool_chunk_set(struct ggml_threadpool * tp, int value);
535535
int ggml_threadpool_chunk_add(struct ggml_threadpool * tp, int value);

ggml/src/ggml-cpu/ggml-cpu.c

Lines changed: 111 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,14 @@ struct ggml_threadpool {
509509
uint32_t poll; // Polling level (0 - no polling)
510510

511511
enum ggml_status ec;
512+
513+
// per-thread timing accumulators (indexed by thread id, size = n_threads)
514+
int64_t * t_compute_us;
515+
int64_t * t_barrier_us;
516+
int64_t * t_poll_us;
517+
int64_t * t_idle_us;
518+
uint64_t * n_compute_ops;
519+
uint64_t * n_barrier_ops;
512520
};
513521

514522
// Per-thread state
@@ -580,7 +588,7 @@ struct ggml_state {
580588

581589
static struct ggml_state g_state = {0};
582590

583-
void ggml_barrier(struct ggml_threadpool * tp) {
591+
void ggml_barrier(struct ggml_threadpool * tp, int ith) {
584592
int n_threads = atomic_load_explicit(&tp->n_graph, memory_order_relaxed) & GGML_THREADPOOL_N_THREADS_MASK;
585593
if (n_threads == 1) {
586594
return;
@@ -607,25 +615,33 @@ void ggml_barrier(struct ggml_threadpool * tp) {
607615
}
608616

609617
// wait for other threads
610-
611-
// Phase 1: brief spin (uses existing poll parameter, same scale as poll_for_work)
612618
{
613-
const uint64_t n_rounds = 1024UL * 128 * tp->poll;
614-
for (uint64_t i = 0; i < n_rounds; i++) {
615-
if (atomic_load_explicit(&tp->n_barrier_passed, memory_order_acquire) != n_passed) {
616-
break;
619+
const int64_t t_barrier_start = ggml_time_us();
620+
621+
// Phase 1: brief spin (uses existing poll parameter, same scale as poll_for_work)
622+
{
623+
const uint64_t n_rounds = 1024UL * 128 * tp->poll;
624+
for (uint64_t i = 0; i < n_rounds; i++) {
625+
if (atomic_load_explicit(&tp->n_barrier_passed, memory_order_acquire) != n_passed) {
626+
break;
627+
}
628+
ggml_thread_cpu_relax();
617629
}
618-
ggml_thread_cpu_relax();
619630
}
620-
}
621631

622-
// Phase 2: sleep on condition variable if threads still haven't arrived
623-
while (atomic_load_explicit(&tp->n_barrier_passed, memory_order_acquire) == n_passed) {
624-
ggml_mutex_lock_shared(&tp->mutex);
625-
// Timed wait to avoid missing the broadcast (spurious wakeup protection)
626-
// 1ms timeout — last thread also increments n_barrier_passed, so we'll see it
627-
ggml_cond_wait_timeout(&tp->cond, &tp->mutex, 1000);
628-
ggml_mutex_unlock_shared(&tp->mutex);
632+
// Phase 2: sleep on condition variable if threads still haven't arrived
633+
while (atomic_load_explicit(&tp->n_barrier_passed, memory_order_acquire) == n_passed) {
634+
ggml_mutex_lock_shared(&tp->mutex);
635+
// Timed wait to avoid missing the broadcast (spurious wakeup protection)
636+
// 1ms timeout — last thread also increments n_barrier_passed, so we'll see it
637+
ggml_cond_wait_timeout(&tp->cond, &tp->mutex, 1000);
638+
ggml_mutex_unlock_shared(&tp->mutex);
639+
}
640+
641+
atomic_fetch_add_explicit(&tp->t_barrier_us[ith],
642+
ggml_time_us() - t_barrier_start, memory_order_relaxed);
643+
atomic_fetch_add_explicit(&tp->n_barrier_ops[ith],
644+
1, memory_order_relaxed);
629645
}
630646

631647
// exit barrier (full seq-cst fence)
@@ -1388,7 +1404,7 @@ UseGgmlGemm1:;
13881404
atomic_store_explicit(&params->threadpool->current_chunk, nth, memory_order_relaxed);
13891405
}
13901406

1391-
ggml_barrier(params->threadpool);
1407+
ggml_barrier(params->threadpool, params->ith);
13921408

13931409
#if GGML_USE_LLAMAFILE
13941410
if (src1->type != vec_dot_type) {
@@ -1669,7 +1685,7 @@ static void ggml_compute_forward_mul_mat_id(
16691685
*current_chunk_ctr = nth;
16701686
}
16711687

1672-
ggml_barrier(params->threadpool);
1688+
ggml_barrier(params->threadpool, params->ith);
16731689

16741690
for (int cur_a = 0; cur_a < n_as; ++cur_a) {
16751691
const int64_t cne1 = matrix_row_counts[cur_a];
@@ -2710,6 +2726,40 @@ static void ggml_thread_cpumask_next(const bool * global_mask, bool * local_mask
27102726
}
27112727
}
27122728

2729+
void ggml_threadpool_print_timings(struct ggml_threadpool * tp) {
2730+
if (!tp) return;
2731+
2732+
const int n_threads = tp->n_threads;
2733+
2734+
GGML_LOG_INFO("\n=== CPU Threadpool Timing (per thread) ===\n");
2735+
GGML_LOG_INFO(" Thread | t_compute_us | t_barrier_us | t_poll_us | t_idle_us | n_compute | n_barrier\n");
2736+
2737+
for (int i = 0; i < n_threads; i++) {
2738+
GGML_LOG_INFO(" %7d | %12" PRId64 " | %12" PRId64 " | %11" PRId64 " | %11" PRId64 " | %9" PRIu64 " | %9" PRIu64 "\n",
2739+
i,
2740+
tp->t_compute_us[i],
2741+
tp->t_barrier_us[i],
2742+
tp->t_poll_us[i],
2743+
tp->t_idle_us[i],
2744+
tp->n_compute_ops[i],
2745+
tp->n_barrier_ops[i]);
2746+
}
2747+
GGML_LOG_INFO("================================================\n\n");
2748+
}
2749+
2750+
void ggml_threadpool_reset_timings(struct ggml_threadpool * tp) {
2751+
if (!tp) return;
2752+
2753+
const size_t timing_size = sizeof(int64_t) * tp->n_threads;
2754+
const size_t count_size = sizeof(uint64_t) * tp->n_threads;
2755+
memset(tp->t_compute_us, 0, timing_size);
2756+
memset(tp->t_barrier_us, 0, timing_size);
2757+
memset(tp->t_poll_us, 0, timing_size);
2758+
memset(tp->t_idle_us, 0, timing_size);
2759+
memset(tp->n_compute_ops, 0, count_size);
2760+
memset(tp->n_barrier_ops, 0, count_size);
2761+
}
2762+
27132763
void ggml_threadpool_free(struct ggml_threadpool* threadpool) {
27142764
if (!threadpool) return;
27152765

@@ -2736,6 +2786,17 @@ void ggml_threadpool_free(struct ggml_threadpool* threadpool) {
27362786
ggml_cond_destroy(&threadpool->cond);
27372787
#endif // GGML_USE_OPENMP
27382788

2789+
// Print timing summary on shutdown
2790+
ggml_threadpool_print_timings(threadpool);
2791+
2792+
// Free timing arrays
2793+
ggml_aligned_free(threadpool->t_compute_us, sizeof(int64_t) * n_threads);
2794+
ggml_aligned_free(threadpool->t_barrier_us, sizeof(int64_t) * n_threads);
2795+
ggml_aligned_free(threadpool->t_poll_us, sizeof(int64_t) * n_threads);
2796+
ggml_aligned_free(threadpool->t_idle_us, sizeof(int64_t) * n_threads);
2797+
ggml_aligned_free(threadpool->n_compute_ops, sizeof(uint64_t) * n_threads);
2798+
ggml_aligned_free(threadpool->n_barrier_ops, sizeof(uint64_t) * n_threads);
2799+
27392800
const size_t workers_size = sizeof(struct ggml_compute_state) * n_threads;
27402801
ggml_aligned_free(threadpool->workers, workers_size);
27412802
ggml_aligned_free(threadpool, sizeof(struct ggml_threadpool));
@@ -3102,7 +3163,7 @@ static thread_ret_t ggml_graph_compute_thread(void * data) {
31023163
}
31033164

31043165
if (node_n + 1 < cgraph->n_nodes) {
3105-
ggml_barrier(state->threadpool);
3166+
ggml_barrier(state->threadpool, state->ith);
31063167
}
31073168
}
31083169

@@ -3112,7 +3173,7 @@ static thread_ret_t ggml_graph_compute_thread(void * data) {
31123173
GGML_PRINT_DEBUG("thread #%d compute-done cplan %p last-graph %d\n", state->ith, (const void *)cplan, state->last_graph);
31133174
#endif
31143175

3115-
ggml_barrier(state->threadpool);
3176+
ggml_barrier(state->threadpool, state->ith);
31163177

31173178
#ifdef GGML_USE_CPU_RISCV64_SPACEMIT
31183179
ggml_backend_cpu_riscv64_spacemit_clear_numa_thread_affinity_threaded(state->ith);
@@ -3214,10 +3275,21 @@ static thread_ret_t ggml_graph_compute_secondary_thread(void* data) {
32143275
// Check if there is new work
32153276
// The main thread is the only one that can dispatch new work
32163277

3217-
ggml_graph_compute_check_for_work(state);
3278+
{
3279+
const int64_t t_poll_start = ggml_time_us();
3280+
ggml_graph_compute_check_for_work(state);
3281+
atomic_fetch_add_explicit(&threadpool->t_poll_us[state->ith],
3282+
ggml_time_us() - t_poll_start, memory_order_relaxed);
3283+
}
32183284
if (state->pending) {
32193285
state->pending = false;
3286+
3287+
const int64_t t_compute_start = ggml_time_us();
32203288
ggml_graph_compute_thread(state);
3289+
atomic_fetch_add_explicit(&threadpool->t_compute_us[state->ith],
3290+
ggml_time_us() - t_compute_start, memory_order_relaxed);
3291+
atomic_fetch_add_explicit(&threadpool->n_compute_ops[state->ith],
3292+
1, memory_order_relaxed);
32213293
}
32223294
}
32233295

@@ -3281,6 +3353,24 @@ static struct ggml_threadpool * ggml_threadpool_new_impl(
32813353
threadpool->poll = tpp->poll;
32823354
threadpool->prio = tpp->prio;
32833355
threadpool->ec = GGML_STATUS_SUCCESS;
3356+
3357+
// Allocate per-thread timing accumulators (zeroed)
3358+
{
3359+
const size_t timing_size = sizeof(int64_t) * tpp->n_threads;
3360+
const size_t count_size = sizeof(uint64_t) * tpp->n_threads;
3361+
threadpool->t_compute_us = (int64_t *) ggml_aligned_malloc(timing_size);
3362+
threadpool->t_barrier_us = (int64_t *) ggml_aligned_malloc(timing_size);
3363+
threadpool->t_poll_us = (int64_t *) ggml_aligned_malloc(timing_size);
3364+
threadpool->t_idle_us = (int64_t *) ggml_aligned_malloc(timing_size);
3365+
threadpool->n_compute_ops = (uint64_t *) ggml_aligned_malloc(count_size);
3366+
threadpool->n_barrier_ops = (uint64_t *) ggml_aligned_malloc(count_size);
3367+
memset(threadpool->t_compute_us, 0, timing_size);
3368+
memset(threadpool->t_barrier_us, 0, timing_size);
3369+
memset(threadpool->t_poll_us, 0, timing_size);
3370+
memset(threadpool->t_idle_us, 0, timing_size);
3371+
memset(threadpool->n_compute_ops, 0, count_size);
3372+
memset(threadpool->n_barrier_ops, 0, count_size);
3373+
}
32843374
}
32853375

32863376
// Allocate and init workers state

ggml/src/ggml-cpu/kleidiai/kleidiai.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ class tensor_traits : public ggml::cpu::tensor_traits {
664664
rhs_kxn, bias, nullptr, rhs_packed, 0, nullptr);
665665
}
666666

667-
ggml_barrier(params->threadpool);
667+
ggml_barrier(params->threadpool, params->ith);
668668

669669
// Matmul (threaded over n)
670670
{
@@ -695,7 +695,7 @@ class tensor_traits : public ggml::cpu::tensor_traits {
695695
}
696696

697697
if (batch_idx != batch_size - 1) {
698-
ggml_barrier(params->threadpool);
698+
ggml_barrier(params->threadpool, params->ith);
699699
}
700700
}
701701

@@ -1057,7 +1057,7 @@ class tensor_traits : public ggml::cpu::tensor_traits {
10571057
}
10581058
}
10591059

1060-
ggml_barrier(params->threadpool);
1060+
ggml_barrier(params->threadpool, params->ith);
10611061

10621062
runtime_slot & slot = runtime[local_slot];
10631063
if (slot.n_cols > 0 && slot.assigned_threads > 0) {
@@ -1103,7 +1103,7 @@ class tensor_traits : public ggml::cpu::tensor_traits {
11031103
}
11041104

11051105
if (batch_idx != ne12 - 1) {
1106-
ggml_barrier(params->threadpool);
1106+
ggml_barrier(params->threadpool, params->ith);
11071107
}
11081108
}
11091109

ggml/src/ggml-cpu/llamafile/sgemm.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ class tinyBLAS {
598598
ggml_threadpool_chunk_set(params->threadpool, params->nth);
599599
}
600600

601-
ggml_barrier(params->threadpool);
601+
ggml_barrier(params->threadpool, params->ith);
602602

603603
int64_t job = params->ith;
604604
while (job < nb_job) {
@@ -627,7 +627,7 @@ class tinyBLAS {
627627
job = ggml_threadpool_chunk_add(params->threadpool, 1);
628628
}
629629

630-
ggml_barrier(params->threadpool);
630+
ggml_barrier(params->threadpool, params->ith);
631631
return;
632632
}
633633

@@ -1164,7 +1164,7 @@ class tinyBLAS_RVV {
11641164
ggml_threadpool_chunk_set(params->threadpool, params->nth);
11651165
}
11661166

1167-
ggml_barrier(params->threadpool);
1167+
ggml_barrier(params->threadpool, params->ith);
11681168

11691169
int64_t job = params->ith;
11701170
while (job < nb_job) {
@@ -1193,7 +1193,7 @@ class tinyBLAS_RVV {
11931193
job = ggml_threadpool_chunk_add(params->threadpool, 1);
11941194
}
11951195

1196-
ggml_barrier(params->threadpool);
1196+
ggml_barrier(params->threadpool, params->ith);
11971197
return;
11981198
}
11991199

0 commit comments

Comments
 (0)