← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ckernel_strict.c
Go to the documentation of this file.
1#include "ckernel_engine.h"
2#include "ck_threadpool.h"
3#include <dlfcn.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <unistd.h>
8#include <errno.h>
9
10#ifdef _OPENMP
11#include <omp.h>
12#endif
13
14#if defined(USE_MKL)
15#include <mkl.h>
16#endif
17
18// =============================================================================
19// Strict parity mode (for numerical reproducibility)
20// =============================================================================
21
22static int ck_strict_parity = 0;
23static float *ck_strict_next_gemm_a = NULL;
25static size_t ck_strict_next_gemm_a_cap = 0;
27
28void ck_set_strict_parity(int enabled)
29{
30 ck_strict_parity = enabled ? 1 : 0;
31 if (!ck_strict_parity) {
34 }
35#ifdef _OPENMP
36 if (ck_strict_parity) {
37 omp_set_dynamic(0);
38 omp_set_num_threads(1);
39 }
40#endif
41}
42
44{
45 return ck_strict_parity;
46}
47
48void ck_strict_store_next_gemm_a(const float *data, size_t elems)
49{
50 if (!ck_strict_parity || !data || elems == 0) {
53 return;
54 }
55 if (elems > ck_strict_next_gemm_a_cap) {
56 float *next = (float *) realloc(ck_strict_next_gemm_a, elems * sizeof(float));
57 if (!next) {
60 return;
61 }
64 }
65 memcpy(ck_strict_next_gemm_a, data, elems * sizeof(float));
68}
69
70const float *ck_strict_consume_next_gemm_a(size_t elems)
71{
73 return NULL;
74 }
77}
78
79typedef void *(*ck_strict_mtmd_clip_init_fn)(const char *, int, int, int, int, int);
80typedef void (*ck_strict_mtmd_clip_free_fn)(void *);
81typedef size_t (*ck_strict_mtmd_clip_embd_nbytes_by_img_fn)(void *, int, int);
82typedef int (*ck_strict_mtmd_clip_encode_float_image_fn)(void *, int, float *, int, int, float *);
83
85 int channels,
86 int height,
87 int width,
88 float *out,
89 size_t out_elems)
90{
91 const char *gguf_path = getenv("CK_STRICT_GGUF_PATH");
92 const char *shim_path = getenv("CK_STRICT_MTMD_SHIM_SO");
93 if (!gguf_path || !gguf_path[0] || !shim_path || !shim_path[0]) {
94 return 0;
95 }
96 if (!planar || !out || channels != 3 || height <= 0 || width <= 0) {
97 return 0;
98 }
99
100 void *shim = dlopen(shim_path, RTLD_LAZY | RTLD_LOCAL);
101 if (!shim) {
102 return 0;
103 }
104
106 (ck_strict_mtmd_clip_init_fn) dlsym(shim, "ck_mtmd_clip_init");
108 (ck_strict_mtmd_clip_free_fn) dlsym(shim, "ck_mtmd_clip_free");
110 (ck_strict_mtmd_clip_embd_nbytes_by_img_fn) dlsym(shim, "ck_mtmd_clip_embd_nbytes_by_img");
112 (ck_strict_mtmd_clip_encode_float_image_fn) dlsym(shim, "ck_mtmd_clip_encode_float_image");
113
114 if (!init_fn || !free_fn || !embd_nbytes_fn || !encode_fn) {
115 dlclose(shim);
116 return 0;
117 }
118
119 const size_t pixel_count = (size_t) height * (size_t) width;
120 float *interleaved = (float *) malloc(pixel_count * (size_t) channels * sizeof(float));
121 if (!interleaved) {
122 dlclose(shim);
123 return 0;
124 }
125 for (size_t idx = 0; idx < pixel_count; ++idx) {
126 interleaved[idx * 3 + 0] = planar[idx];
127 interleaved[idx * 3 + 1] = planar[pixel_count + idx];
128 interleaved[idx * 3 + 2] = planar[2 * pixel_count + idx];
129 }
130
131 int ok = 0;
132 void *handle = init_fn(gguf_path, 0, 0, 0, 0, 0);
133 if (handle) {
134 const size_t needed_bytes = embd_nbytes_fn(handle, width, height);
135 if (needed_bytes > 0 && needed_bytes <= out_elems * sizeof(float)) {
136 ok = encode_fn(handle, 1, interleaved, height, width, out) ? 1 : 0;
137 }
138 free_fn(handle);
139 }
140
141 free(interleaved);
142 dlclose(shim);
143 return ok;
144}
145
146// =============================================================================
147// Thread configuration
148// =============================================================================
149
150static int g_num_threads = 0;
152
153static int ck_parse_env_int(const char *name)
154{
155 const char *val = getenv(name);
156 if (!val || !val[0]) {
157 return 0;
158 }
159
160 errno = 0;
161 char *end = NULL;
162 long n = strtol(val, &end, 10);
163 if (errno != 0 || end == val || n <= 0 || n > (1L << 20)) {
164 return 0;
165 }
166 return (int)n;
167}
168
169// Detect physical CPU cores (not hyperthreads) when possible.
171{
172 int physical_cores = 0;
173 int logical_cores = (int)sysconf(_SC_NPROCESSORS_ONLN);
174 if (logical_cores <= 0) {
175 logical_cores = 1;
176 }
177
178 int cpu_cores_hint = 0;
179 int siblings_hint = 0;
180
181 // Read from /proc/cpuinfo (Linux) and count unique (physical id, core id) pairs.
182 FILE *f = fopen("/proc/cpuinfo", "r");
183 if (f) {
184 char line[256];
185 int physical_id = -1;
186 int core_id = -1;
187
188 struct {
189 int physical_id;
190 int core_id;
191 } seen[8192];
192 int seen_count = 0;
193
194 const int seen_cap = (int)(sizeof(seen) / sizeof(seen[0]));
195
196 // Helper: add (pid,cid) to set if not present.
197 #define CK_ADD_PAIR(pid, cid) \
198 do { \
199 if ((pid) >= 0 && (cid) >= 0) { \
200 int exists = 0; \
201 for (int ii = 0; ii < seen_count; ++ii) { \
202 if (seen[ii].physical_id == (pid) && \
203 seen[ii].core_id == (cid)) { \
204 exists = 1; \
205 break; \
206 } \
207 } \
208 if (!exists && seen_count < seen_cap) { \
209 seen[seen_count].physical_id = (pid); \
210 seen[seen_count].core_id = (cid); \
211 ++seen_count; \
212 } \
213 } \
214 } while (0)
215
216 while (fgets(line, sizeof(line), f)) {
217 int val;
218
219 // Blank line separates processor blocks.
220 if (line[0] == '\n' || line[0] == '\0') {
221 CK_ADD_PAIR(physical_id, core_id);
222 physical_id = -1;
223 core_id = -1;
224 continue;
225 }
226
227 if (sscanf(line, "physical id : %d", &val) == 1) {
228 physical_id = val;
229 continue;
230 }
231 if (sscanf(line, "core id : %d", &val) == 1) {
232 core_id = val;
233 continue;
234 }
235 if (sscanf(line, "cpu cores : %d", &val) == 1) {
236 if (val > cpu_cores_hint) cpu_cores_hint = val;
237 continue;
238 }
239 if (sscanf(line, "siblings : %d", &val) == 1) {
240 if (val > siblings_hint) siblings_hint = val;
241 continue;
242 }
243 }
244 fclose(f);
245
246 // Handle file without trailing blank line.
247 CK_ADD_PAIR(physical_id, core_id);
248
249 #undef CK_ADD_PAIR
250
251 physical_cores = seen_count;
252 }
253
254 // Fallback: infer threads-per-core from siblings/cpu cores when pair data
255 // is missing (common in containers/VMs).
256 if (physical_cores <= 1 && logical_cores > 1) {
257 int threads_per_core = 0;
258 if (siblings_hint > 0 && cpu_cores_hint > 0 && siblings_hint >= cpu_cores_hint) {
259 threads_per_core = siblings_hint / cpu_cores_hint;
260 }
261 if (threads_per_core > 1) {
262 int inferred_physical = logical_cores / threads_per_core;
263 if (inferred_physical > 1) {
264 return inferred_physical;
265 }
266 }
267 if (cpu_cores_hint > 1 && cpu_cores_hint <= logical_cores) {
268 return cpu_cores_hint;
269 }
270 return logical_cores;
271 }
272
273 if (physical_cores > 1) {
274 return physical_cores;
275 }
276
277 return logical_cores;
278}
279
280void ck_set_num_threads(int num_threads)
281{
282 // 0 = auto-detect
283 if (num_threads <= 0) {
284 // Prefer explicit env controls when present:
285 // - CK_NUM_THREADS: engine-level override
286 // - OMP_NUM_THREADS: standard OpenMP control (set by `ck run --threads`)
287 int env_threads = ck_parse_env_int("CK_NUM_THREADS");
288 if (env_threads <= 0) {
289 env_threads = ck_parse_env_int("OMP_NUM_THREADS");
290 }
291 num_threads = env_threads > 0 ? env_threads : ck_get_physical_cores();
292 }
293
294 g_num_threads = num_threads;
296
297#ifdef _OPENMP
298 omp_set_dynamic(0); // Disable dynamic adjustment
299 omp_set_num_threads(num_threads);
300#endif
301
302#if defined(USE_MKL)
303 mkl_set_num_threads(num_threads);
304#endif
305
306 fprintf(stderr, "[CK] Set %d threads (auto=%d)\n",
307 num_threads, ck_get_physical_cores());
308}
309
311{
312 // Auto-initialize if not set
314 ck_set_num_threads(0); // Auto-detect
315 }
316 return g_num_threads;
317}
318
319// =============================================================================
320// Thread pool lifecycle
321// =============================================================================
322
323/**
324 * Initialize the global thread pool.
325 * Called once during engine startup (e.g., from ck_model_init).
326 * Uses ck_get_num_threads() for thread count (respects CK_NUM_THREADS env).
327 *
328 * Safe to call multiple times — subsequent calls are no-ops.
329 */
331{
332 /* ck_threadpool_global() uses pthread_once internally */
333 ck_threadpool_t *pool = ck_threadpool_global();
334 (void)pool;
335}
336
337/**
338 * Shut down the global thread pool.
339 * Called during engine teardown. Workers are joined and freed.
340 */
345
346/**
347 * Get the global thread pool handle for dispatch.
348 * Convenience wrapper — initializes on first call.
349 */
350ck_threadpool_t *ck_get_threadpool(void)
351{
352 return ck_threadpool_global();
353}
Persistent pthread thread pool for CK-Engine inference.
ck_threadpool_t * ck_threadpool_global(void)
void ck_threadpool_global_destroy(void)
static int ck_parse_env_int(const char *name)
void ck_threadpool_init(void)
void ck_threadpool_shutdown(void)
void ck_set_num_threads(int num_threads)
static size_t ck_strict_next_gemm_a_size
static size_t ck_strict_next_gemm_a_cap
static int g_num_threads
void ck_strict_store_next_gemm_a(const float *data, size_t elems)
static float * ck_strict_next_gemm_a
static int ck_strict_parity
const float * ck_strict_consume_next_gemm_a(size_t elems)
int ck_get_physical_cores(void)
void ck_set_strict_parity(int enabled)
void(* ck_strict_mtmd_clip_free_fn)(void *)
int ck_strict_mtmd_clip_encode_planar_f32(const float *planar, int channels, int height, int width, float *out, size_t out_elems)
static int ck_strict_next_gemm_a_valid
void *(* ck_strict_mtmd_clip_init_fn)(const char *, int, int, int, int, int)
#define CK_ADD_PAIR(pid, cid)
static int g_threads_initialized
ck_threadpool_t * ck_get_threadpool(void)
int ck_get_num_threads(void)
int ck_strict_parity_enabled(void)
size_t(* ck_strict_mtmd_clip_embd_nbytes_by_img_fn)(void *, int, int)
int(* ck_strict_mtmd_clip_encode_float_image_fn)(void *, int, float *, int, int, float *)
uint32_t end
Definition utf8.c:215