@@ -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
581589static 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+
27132763void 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
0 commit comments