← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
optimizer_kernels.c
Go to the documentation of this file.
1/**
2 * @file optimizer_kernels.c
3 * @brief Optimizer kernels for training (AdamW, SGD)
4 *
5 * CK-ENGINE KERNEL RULES:
6 * =======================
7 * 1. NO malloc/free - memory via bump allocator, pointers passed in
8 * 2. NO OpenMP - parallelization at orchestrator/codegen layer
9 * 3. API must define: inputs, outputs, workspace, and memory layouts
10 * 4. Pure computation - deterministic, no side effects
11 *
12 * After changes: make test && make llamacpp-parity-full
13 *
14 * AdamW Algorithm:
15 * m_t = beta1 * m_{t-1} + (1 - beta1) * g_t
16 * v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2
17 * m_hat = m_t / (1 - beta1^t)
18 * v_hat = v_t / (1 - beta2^t)
19 * w_t = w_{t-1} - lr * (m_hat / (sqrt(v_hat) + eps) + weight_decay * w_{t-1})
20 *
21 * Note: AdamW applies weight decay directly to weights, not to gradients.
22 * This is different from L2 regularization (Adam with L2 adds decay to gradient).
23 *
24 * Epsilon amplification at early steps: at step 1 with bc2=0.001, elements where
25 * vā‰ˆ0 (sparse/near-zero gradients) produce sqrt(v_hat)+eps ā‰ˆ eps=1e-8, amplifying
26 * any fp32 rounding in the accumulated gradient by up to lr/eps = 1e6. This is
27 * expected AdamW behavior, not a bug. In parity tests, gate on mean_param_diff
28 * (not max_param_diff) for grad_accum > 1 to avoid false alarms from these outliers.
29 *
30 * Long-horizon fp32 risk: at lr=1e-3 with all-fp32 SIMD paths, known drift begins
31 * around step ~800 due to accumulated rounding. Use ck_strict_parity_enabled() (fp64
32 * path) for parity validation; for production training keep lr < 1e-3 or monitor.
33 */
34
35#include <math.h>
36#include <stddef.h>
37#include <stdint.h>
38#include <string.h>
39#include "ck_threadpool.h"
40#include "ckernel_engine.h"
41
42/* Include SIMD headers based on available instruction sets */
43#if defined(__AVX512F__) || defined(__AVX__) || defined(__SSE2__)
44#include <immintrin.h>
45#endif
46
47#define CK_OPT_PAR_MIN_NUMEL ((size_t)262144)
48#define CK_OPT_PAR_MAX_THREADS 256
49
50typedef struct {
51 const float *grad;
52 float *weight;
53 float *m;
54 float *v;
55 size_t numel;
56 float lr;
57 float beta1;
58 float beta2;
59 float eps;
60 float weight_decay;
61 int step;
62} ck_adamw_parallel_args_t;
63
64typedef struct {
65 float *dst;
66 const float *src;
67 size_t numel;
68} ck_accum_parallel_args_t;
69
70typedef struct {
71 float *const *dsts;
72 const float *const *srcs;
73 const size_t *numels;
74 int tensor_count;
75 size_t total_numel;
76} ck_accum_multi_parallel_args_t;
77
78typedef struct {
79 float *grad;
80 size_t numel;
81 float scale;
82} ck_scale_parallel_args_t;
83
84typedef struct {
85 const float *grad;
86 size_t numel;
87 double partial[CK_OPT_PAR_MAX_THREADS];
88} ck_sum_sq_parallel_args_t;
89
90typedef struct {
91 const float *const *grads;
92 const size_t *numels;
93 int tensor_count;
94 double partial[CK_OPT_PAR_MAX_THREADS];
95} ck_sum_sq_multi_parallel_args_t;
96
97typedef struct {
98 float *const *grads;
99 float *const *weights;
100 float *const *m_states;
101 float *const *v_states;
102 const size_t *numels;
103 int tensor_count;
104 float lr;
105 float beta1;
106 float beta2;
107 float eps;
108 float weight_decay;
109 float grad_scale;
110 int step;
111} ck_adamw_multi_parallel_args_t;
112
113static void adamw_update_f32_impl(
114 const float *grad,
115 float *weight,
116 float *m,
117 float *v,
118 size_t numel,
119 float lr,
120 float beta1,
121 float beta2,
122 float eps,
123 float weight_decay,
124 int step);
125
126static void gradient_accumulate_f32_impl(float *dst, const float *src, size_t numel);
127static void gradient_scale_f32_impl(float *grad, size_t numel, float scale);
128static double gradient_sum_sq_f32_impl(const float *grad, size_t numel);
129
130static int ck_opt_pick_active_threads(int nth, size_t work_items, size_t min_chunk)
131{
132 if (nth <= 1 || work_items == 0 || min_chunk == 0) {
133 return 1;
134 }
135 size_t active = (work_items + min_chunk - 1u) / min_chunk;
136 if (active < 1u) {
137 active = 1u;
138 }
139 if (active > (size_t)nth) {
140 active = (size_t)nth;
141 }
142 return (int)active;
143}
144
145static void ck_adamw_parallel_work(int ith, int nth, void *argp)
146{
147 ck_adamw_parallel_args_t *a = (ck_adamw_parallel_args_t *)argp;
148 if (!a || !a->grad || !a->weight || !a->m || !a->v || a->numel == 0) {
149 return;
150 }
151 size_t chunk = (a->numel + (size_t)nth - 1u) / (size_t)nth;
152 size_t start = (size_t)ith * chunk;
153 if (start >= a->numel) {
154 return;
155 }
156 size_t end = start + chunk;
157 if (end > a->numel) {
158 end = a->numel;
159 }
161 a->grad + start,
162 a->weight + start,
163 a->m + start,
164 a->v + start,
165 end - start,
166 a->lr,
167 a->beta1,
168 a->beta2,
169 a->eps,
170 a->weight_decay,
171 a->step);
172}
173
174static void ck_accum_parallel_work(int ith, int nth, void *argp)
175{
176 ck_accum_parallel_args_t *a = (ck_accum_parallel_args_t *)argp;
177 if (!a || !a->dst || !a->src || a->numel == 0) {
178 return;
179 }
180 size_t chunk = (a->numel + (size_t)nth - 1u) / (size_t)nth;
181 size_t start = (size_t)ith * chunk;
182 if (start >= a->numel) {
183 return;
184 }
185 size_t end = start + chunk;
186 if (end > a->numel) {
187 end = a->numel;
188 }
189 gradient_accumulate_f32_impl(a->dst + start, a->src + start, end - start);
190}
191
192static void ck_accum_multi_parallel_work(int ith, int nth, void *argp)
193{
194 ck_accum_multi_parallel_args_t *a = (ck_accum_multi_parallel_args_t *)argp;
195 if (!a || !a->dsts || !a->srcs || !a->numels || a->tensor_count <= 0 || a->total_numel == 0) {
196 return;
197 }
198
199 size_t chunk = (a->total_numel + (size_t)nth - 1u) / (size_t)nth;
200 size_t start = (size_t)ith * chunk;
201 if (start >= a->total_numel) {
202 return;
203 }
204 size_t end = start + chunk;
205 if (end > a->total_numel) {
206 end = a->total_numel;
207 }
208
209 size_t cursor = 0;
210 for (int ti = 0; ti < a->tensor_count; ++ti) {
211 float *dst = a->dsts[ti];
212 const float *src = a->srcs[ti];
213 size_t n = a->numels[ti];
214 if (!dst || !src || n == 0) {
215 continue;
216 }
217 size_t next = cursor + n;
218 if (end <= cursor) {
219 break;
220 }
221 if (start < next && end > cursor) {
222 size_t local_start = (start > cursor) ? (start - cursor) : 0u;
223 size_t local_end = (end < next) ? (end - cursor) : n;
224 if (local_end > local_start) {
225 gradient_accumulate_f32_impl(dst + local_start, src + local_start, local_end - local_start);
226 }
227 }
228 cursor = next;
229 }
230}
231
232static void ck_scale_parallel_work(int ith, int nth, void *argp)
233{
234 ck_scale_parallel_args_t *a = (ck_scale_parallel_args_t *)argp;
235 if (!a || !a->grad || a->numel == 0) {
236 return;
237 }
238 size_t chunk = (a->numel + (size_t)nth - 1u) / (size_t)nth;
239 size_t start = (size_t)ith * chunk;
240 if (start >= a->numel) {
241 return;
242 }
243 size_t end = start + chunk;
244 if (end > a->numel) {
245 end = a->numel;
246 }
247 gradient_scale_f32_impl(a->grad + start, end - start, a->scale);
248}
249
250static void ck_sum_sq_parallel_work(int ith, int nth, void *argp)
251{
252 ck_sum_sq_parallel_args_t *a = (ck_sum_sq_parallel_args_t *)argp;
253 if (!a || !a->grad || a->numel == 0 || ith < 0 || ith >= CK_OPT_PAR_MAX_THREADS) {
254 return;
255 }
256 size_t chunk = (a->numel + (size_t)nth - 1u) / (size_t)nth;
257 size_t start = (size_t)ith * chunk;
258 if (start >= a->numel) {
259 a->partial[ith] = 0.0;
260 return;
261 }
262 size_t end = start + chunk;
263 if (end > a->numel) {
264 end = a->numel;
265 }
266 a->partial[ith] = gradient_sum_sq_f32_impl(a->grad + start, end - start);
267}
268
269static void ck_sum_sq_multi_parallel_work(int ith, int nth, void *argp)
270{
271 ck_sum_sq_multi_parallel_args_t *a = (ck_sum_sq_multi_parallel_args_t *)argp;
272 if (!a || !a->grads || !a->numels || a->tensor_count <= 0 ||
273 ith < 0 || ith >= CK_OPT_PAR_MAX_THREADS) {
274 return;
275 }
276
277 double sum_sq = 0.0;
278 for (int ti = ith; ti < a->tensor_count; ti += nth) {
279 const float *g = a->grads[ti];
280 size_t n = a->numels[ti];
281 if (!g || n == 0) {
282 continue;
283 }
284 sum_sq += gradient_sum_sq_f32_impl(g, n);
285 }
286 a->partial[ith] = sum_sq;
287}
288
289static void ck_adamw_multi_parallel_work(int ith, int nth, void *argp)
290{
291 ck_adamw_multi_parallel_args_t *a = (ck_adamw_multi_parallel_args_t *)argp;
292 if (!a || !a->grads || !a->weights || !a->m_states || !a->v_states || !a->numels ||
293 a->tensor_count <= 0) {
294 return;
295 }
296
297 const int per = (a->tensor_count + nth - 1) / nth;
298 const int t0 = per * ith;
299 int t1 = t0 + per;
300 if (t0 >= a->tensor_count) {
301 return;
302 }
303 if (t1 > a->tensor_count) {
304 t1 = a->tensor_count;
305 }
306
307 for (int ti = t0; ti < t1; ++ti) {
308 float *g = a->grads[ti];
309 float *w = a->weights[ti];
310 float *m = a->m_states[ti];
311 float *v = a->v_states[ti];
312 size_t n = a->numels[ti];
313 if (!g || !w || !m || !v || n == 0) {
314 continue;
315 }
316
317 if (a->grad_scale != 1.0f) {
318 gradient_scale_f32_impl(g, n, a->grad_scale);
319 }
320
322 g,
323 w,
324 m,
325 v,
326 n,
327 a->lr,
328 a->beta1,
329 a->beta2,
330 a->eps,
331 a->weight_decay,
332 a->step);
333 }
334}
335
336
337/**
338 * @brief AdamW optimizer update (fp32 version)
339 *
340 * Updates weights in-place using the AdamW algorithm.
341 * Momentum (m) and variance (v) are stored in fp32 for numerical stability.
342 *
343 * @param grad Gradient tensor (fp32) [numel]
344 * @param weight Weight tensor to update (fp32, in-place) [numel]
345 * @param m First moment (momentum) buffer (fp32, in-place) [numel]
346 * @param v Second moment (variance) buffer (fp32, in-place) [numel]
347 * @param numel Number of elements
348 * @param lr Learning rate
349 * @param beta1 Exponential decay rate for first moment (typically 0.9)
350 * @param beta2 Exponential decay rate for second moment (typically 0.999)
351 * @param eps Small constant for numerical stability (typically 1e-8)
352 * @param weight_decay Weight decay coefficient (typically 0.01)
353 * @param step Current step number (1-indexed for bias correction)
354 */
356 const float *grad,
357 float *weight,
358 float *m,
359 float *v,
360 size_t numel,
361 float lr,
362 float beta1,
363 float beta2,
364 float eps,
365 float weight_decay,
366 int step)
367{
368 if (!grad || !weight || !m || !v || numel == 0) {
369 return;
370 }
371
372 // Bias correction terms
373 float bias_correction1 = 1.0f - powf(beta1, (float)step);
374 float bias_correction2 = 1.0f - powf(beta2, (float)step);
375
376 // Precompute constants
377 float one_minus_beta1 = 1.0f - beta1;
378 float one_minus_beta2 = 1.0f - beta2;
379
381 const double beta1_d = (double)beta1;
382 const double beta2_d = (double)beta2;
383 const double one_minus_beta1_d = 1.0 - beta1_d;
384 const double one_minus_beta2_d = 1.0 - beta2_d;
385 const double lr_d = (double)lr;
386 const double eps_d = (double)eps;
387 const double wd_d = (double)weight_decay;
388
389 const double bc1 = 1.0 - pow(beta1_d, (double)step);
390 const double bc2 = 1.0 - pow(beta2_d, (double)step);
391 const double step_size = lr_d / bc1;
392 const double bc2_sqrt = sqrt(bc2);
393 const double wd_scale = 1.0 - lr_d * wd_d;
394
395 for (size_t i = 0; i < numel; ++i) {
396 double g = (double)grad[i];
397 double w = (double)weight[i];
398 double m_i = (double)m[i];
399 double v_i = (double)v[i];
400
401 m_i = beta1_d * m_i + one_minus_beta1_d * g;
402 v_i = beta2_d * v_i + one_minus_beta2_d * g * g;
403
404 // Match PyTorch AdamW op order: decoupled weight decay first.
405 w *= wd_scale;
406
407 double denom = sqrt(v_i) / bc2_sqrt + eps_d;
408 w -= step_size * (m_i / denom);
409
410 m[i] = (float)m_i;
411 v[i] = (float)v_i;
412 weight[i] = (float)w;
413 }
414 return;
415 }
416
417#if defined(__AVX512F__)
418 // AVX-512 path: process 16 floats at a time
419 __m512 v_beta1 = _mm512_set1_ps(beta1);
420 __m512 v_beta2 = _mm512_set1_ps(beta2);
421 __m512 v_one_minus_beta1 = _mm512_set1_ps(one_minus_beta1);
422 __m512 v_one_minus_beta2 = _mm512_set1_ps(one_minus_beta2);
423 __m512 v_lr = _mm512_set1_ps(lr);
424 __m512 v_eps = _mm512_set1_ps(eps);
425 __m512 v_weight_decay = _mm512_set1_ps(weight_decay);
426 __m512 v_bc1_inv = _mm512_set1_ps(1.0f / bias_correction1);
427 __m512 v_bc2_inv = _mm512_set1_ps(1.0f / bias_correction2);
428
429 size_t i = 0;
430 for (; i + 16 <= numel; i += 16) {
431 __m512 g = _mm512_loadu_ps(&grad[i]);
432 __m512 w = _mm512_loadu_ps(&weight[i]);
433 __m512 m_val = _mm512_loadu_ps(&m[i]);
434 __m512 v_val = _mm512_loadu_ps(&v[i]);
435
436 // m = beta1 * m + (1 - beta1) * g
437 m_val = _mm512_fmadd_ps(v_beta1, m_val, _mm512_mul_ps(v_one_minus_beta1, g));
438
439 // v = beta2 * v + (1 - beta2) * g^2
440 __m512 g_sq = _mm512_mul_ps(g, g);
441 v_val = _mm512_fmadd_ps(v_beta2, v_val, _mm512_mul_ps(v_one_minus_beta2, g_sq));
442
443 // Bias-corrected estimates
444 __m512 m_hat = _mm512_mul_ps(m_val, v_bc1_inv);
445 __m512 v_hat = _mm512_mul_ps(v_val, v_bc2_inv);
446
447 // w = w - lr * (m_hat / (sqrt(v_hat) + eps) + weight_decay * w)
448 __m512 denom = _mm512_add_ps(_mm512_sqrt_ps(v_hat), v_eps);
449 __m512 update = _mm512_div_ps(m_hat, denom);
450 update = _mm512_fmadd_ps(v_weight_decay, w, update);
451 w = _mm512_fnmadd_ps(v_lr, update, w);
452
453 _mm512_storeu_ps(&weight[i], w);
454 _mm512_storeu_ps(&m[i], m_val);
455 _mm512_storeu_ps(&v[i], v_val);
456 }
457
458 // Scalar tail
459 for (; i < numel; ++i) {
460 float g = grad[i];
461 float w = weight[i];
462 m[i] = beta1 * m[i] + one_minus_beta1 * g;
463 v[i] = beta2 * v[i] + one_minus_beta2 * g * g;
464 float m_hat = m[i] / bias_correction1;
465 float v_hat = v[i] / bias_correction2;
466 weight[i] = w - lr * (m_hat / (sqrtf(v_hat) + eps) + weight_decay * w);
467 }
468
469#elif defined(__AVX__)
470 // AVX path: process 8 floats at a time (no FMA on older CPUs like Ivy Bridge)
471 __m256 v_beta1 = _mm256_set1_ps(beta1);
472 __m256 v_beta2 = _mm256_set1_ps(beta2);
473 __m256 v_one_minus_beta1 = _mm256_set1_ps(one_minus_beta1);
474 __m256 v_one_minus_beta2 = _mm256_set1_ps(one_minus_beta2);
475 __m256 v_lr = _mm256_set1_ps(lr);
476 __m256 v_eps = _mm256_set1_ps(eps);
477 __m256 v_weight_decay = _mm256_set1_ps(weight_decay);
478 __m256 v_bc1_inv = _mm256_set1_ps(1.0f / bias_correction1);
479 __m256 v_bc2_inv = _mm256_set1_ps(1.0f / bias_correction2);
480
481 size_t i = 0;
482 for (; i + 8 <= numel; i += 8) {
483 __m256 g = _mm256_loadu_ps(&grad[i]);
484 __m256 w = _mm256_loadu_ps(&weight[i]);
485 __m256 m_val = _mm256_loadu_ps(&m[i]);
486 __m256 v_val = _mm256_loadu_ps(&v[i]);
487
488 // m = beta1 * m + (1 - beta1) * g
489 m_val = _mm256_add_ps(_mm256_mul_ps(v_beta1, m_val),
490 _mm256_mul_ps(v_one_minus_beta1, g));
491
492 // v = beta2 * v + (1 - beta2) * g^2
493 __m256 g_sq = _mm256_mul_ps(g, g);
494 v_val = _mm256_add_ps(_mm256_mul_ps(v_beta2, v_val),
495 _mm256_mul_ps(v_one_minus_beta2, g_sq));
496
497 // Bias-corrected estimates
498 __m256 m_hat = _mm256_mul_ps(m_val, v_bc1_inv);
499 __m256 v_hat = _mm256_mul_ps(v_val, v_bc2_inv);
500
501 // w = w - lr * (m_hat / (sqrt(v_hat) + eps) + weight_decay * w)
502 __m256 denom = _mm256_add_ps(_mm256_sqrt_ps(v_hat), v_eps);
503 __m256 update = _mm256_div_ps(m_hat, denom);
504 update = _mm256_add_ps(update, _mm256_mul_ps(v_weight_decay, w));
505 w = _mm256_sub_ps(w, _mm256_mul_ps(v_lr, update));
506
507 _mm256_storeu_ps(&weight[i], w);
508 _mm256_storeu_ps(&m[i], m_val);
509 _mm256_storeu_ps(&v[i], v_val);
510 }
511
512 // Scalar tail
513 for (; i < numel; ++i) {
514 float g = grad[i];
515 float w = weight[i];
516 m[i] = beta1 * m[i] + one_minus_beta1 * g;
517 v[i] = beta2 * v[i] + one_minus_beta2 * g * g;
518 float m_hat = m[i] / bias_correction1;
519 float v_hat = v[i] / bias_correction2;
520 weight[i] = w - lr * (m_hat / (sqrtf(v_hat) + eps) + weight_decay * w);
521 }
522
523#elif defined(__SSE2__)
524 // SSE2 path: process 4 floats at a time
525 __m128 v_beta1 = _mm_set1_ps(beta1);
526 __m128 v_beta2 = _mm_set1_ps(beta2);
527 __m128 v_one_minus_beta1 = _mm_set1_ps(one_minus_beta1);
528 __m128 v_one_minus_beta2 = _mm_set1_ps(one_minus_beta2);
529 __m128 v_lr = _mm_set1_ps(lr);
530 __m128 v_eps = _mm_set1_ps(eps);
531 __m128 v_weight_decay = _mm_set1_ps(weight_decay);
532 __m128 v_bc1_inv = _mm_set1_ps(1.0f / bias_correction1);
533 __m128 v_bc2_inv = _mm_set1_ps(1.0f / bias_correction2);
534
535 size_t i = 0;
536 for (; i + 4 <= numel; i += 4) {
537 __m128 g = _mm_loadu_ps(&grad[i]);
538 __m128 w = _mm_loadu_ps(&weight[i]);
539 __m128 m_val = _mm_loadu_ps(&m[i]);
540 __m128 v_val = _mm_loadu_ps(&v[i]);
541
542 // m = beta1 * m + (1 - beta1) * g
543 m_val = _mm_add_ps(_mm_mul_ps(v_beta1, m_val),
544 _mm_mul_ps(v_one_minus_beta1, g));
545
546 // v = beta2 * v + (1 - beta2) * g^2
547 __m128 g_sq = _mm_mul_ps(g, g);
548 v_val = _mm_add_ps(_mm_mul_ps(v_beta2, v_val),
549 _mm_mul_ps(v_one_minus_beta2, g_sq));
550
551 // Bias-corrected estimates
552 __m128 m_hat = _mm_mul_ps(m_val, v_bc1_inv);
553 __m128 v_hat = _mm_mul_ps(v_val, v_bc2_inv);
554
555 // w = w - lr * (m_hat / (sqrt(v_hat) + eps) + weight_decay * w)
556 __m128 denom = _mm_add_ps(_mm_sqrt_ps(v_hat), v_eps);
557 __m128 update = _mm_div_ps(m_hat, denom);
558 update = _mm_add_ps(update, _mm_mul_ps(v_weight_decay, w));
559 w = _mm_sub_ps(w, _mm_mul_ps(v_lr, update));
560
561 _mm_storeu_ps(&weight[i], w);
562 _mm_storeu_ps(&m[i], m_val);
563 _mm_storeu_ps(&v[i], v_val);
564 }
565
566 // Scalar tail
567 for (; i < numel; ++i) {
568 float g = grad[i];
569 float w = weight[i];
570 m[i] = beta1 * m[i] + one_minus_beta1 * g;
571 v[i] = beta2 * v[i] + one_minus_beta2 * g * g;
572 float m_hat = m[i] / bias_correction1;
573 float v_hat = v[i] / bias_correction2;
574 weight[i] = w - lr * (m_hat / (sqrtf(v_hat) + eps) + weight_decay * w);
575 }
576
577#else
578 // Scalar path
579 for (size_t i = 0; i < numel; ++i) {
580 float g = grad[i];
581 float w = weight[i];
582 m[i] = beta1 * m[i] + one_minus_beta1 * g;
583 v[i] = beta2 * v[i] + one_minus_beta2 * g * g;
584 float m_hat = m[i] / bias_correction1;
585 float v_hat = v[i] / bias_correction2;
586 weight[i] = w - lr * (m_hat / (sqrtf(v_hat) + eps) + weight_decay * w);
587 }
588#endif
589}
590
591
593 const float *grad,
594 float *weight,
595 float *m,
596 float *v,
597 size_t numel,
598 float lr,
599 float beta1,
600 float beta2,
601 float eps,
602 float weight_decay,
603 int step)
604{
605 if (!grad || !weight || !m || !v || numel == 0) {
606 return;
607 }
608
609 ck_threadpool_t *pool = ck_threadpool_global();
610 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
611 if (!pool || nth <= 1 || nth > CK_OPT_PAR_MAX_THREADS || numel < CK_OPT_PAR_MIN_NUMEL) {
612 adamw_update_f32_impl(grad, weight, m, v, numel, lr, beta1, beta2, eps, weight_decay, step);
613 return;
614 }
615 int active_nth = ck_opt_pick_active_threads(nth, numel, CK_OPT_PAR_MIN_NUMEL);
616 if (active_nth <= 1) {
617 adamw_update_f32_impl(grad, weight, m, v, numel, lr, beta1, beta2, eps, weight_decay, step);
618 return;
619 }
620
621 ck_adamw_parallel_args_t args = {
622 .grad = grad,
623 .weight = weight,
624 .m = m,
625 .v = v,
626 .numel = numel,
627 .lr = lr,
628 .beta1 = beta1,
629 .beta2 = beta2,
630 .eps = eps,
631 .weight_decay = weight_decay,
632 .step = step,
633 };
634 ck_threadpool_dispatch_n(pool, active_nth, ck_adamw_parallel_work, &args);
635}
636
637
639 float *const *grads,
640 float *const *weights,
641 float *const *m_states,
642 float *const *v_states,
643 const size_t *numels,
644 int tensor_count,
645 float lr,
646 float beta1,
647 float beta2,
648 float eps,
649 float weight_decay,
650 float max_grad_norm,
651 int step)
652{
653 if (!grads || !weights || !m_states || !v_states || !numels || tensor_count <= 0) {
654 return;
655 }
656
657 size_t total_numel = 0;
658 int valid_tensors = 0;
659 for (int i = 0; i < tensor_count; ++i) {
660 if (grads[i] && weights[i] && m_states[i] && v_states[i] && numels[i] > 0) {
661 total_numel += numels[i];
662 valid_tensors += 1;
663 }
664 }
665 if (valid_tensors == 0 || total_numel == 0) {
666 return;
667 }
668
669 float grad_scale = 1.0f;
670 if (max_grad_norm > 0.0f) {
671 float global_norm = gradient_global_norm_multi_f32((const float *const *)grads, numels, tensor_count);
672 if (global_norm > max_grad_norm) {
673 grad_scale = max_grad_norm / global_norm;
674 }
675 }
676
677 ck_threadpool_t *pool = ck_threadpool_global();
678 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
679
680 if (!pool || nth <= 1 || nth > CK_OPT_PAR_MAX_THREADS ||
681 total_numel < CK_OPT_PAR_MIN_NUMEL || valid_tensors < 2) {
682 for (int i = 0; i < tensor_count; ++i) {
683 float *g = grads[i];
684 float *w = weights[i];
685 float *m = m_states[i];
686 float *v = v_states[i];
687 size_t n = numels[i];
688 if (!g || !w || !m || !v || n == 0) {
689 continue;
690 }
691 if (grad_scale != 1.0f) {
692 gradient_scale_f32_impl(g, n, grad_scale);
693 }
694 adamw_update_f32_impl(g, w, m, v, n, lr, beta1, beta2, eps, weight_decay, step);
695 }
696 return;
697 }
698 int active_nth = ck_opt_pick_active_threads(nth, total_numel, CK_OPT_PAR_MIN_NUMEL);
699 if (active_nth > valid_tensors) {
700 active_nth = valid_tensors;
701 }
702 if (active_nth <= 1) {
703 for (int i = 0; i < tensor_count; ++i) {
704 float *g = grads[i];
705 float *w = weights[i];
706 float *m = m_states[i];
707 float *v = v_states[i];
708 size_t n = numels[i];
709 if (!g || !w || !m || !v || n == 0) {
710 continue;
711 }
712 if (grad_scale != 1.0f) {
713 gradient_scale_f32_impl(g, n, grad_scale);
714 }
715 adamw_update_f32_impl(g, w, m, v, n, lr, beta1, beta2, eps, weight_decay, step);
716 }
717 return;
718 }
719
720 ck_adamw_multi_parallel_args_t args = {
721 .grads = grads,
722 .weights = weights,
723 .m_states = m_states,
724 .v_states = v_states,
725 .numels = numels,
726 .tensor_count = tensor_count,
727 .lr = lr,
728 .beta1 = beta1,
729 .beta2 = beta2,
730 .eps = eps,
731 .weight_decay = weight_decay,
732 .grad_scale = grad_scale,
733 .step = step,
734 };
736}
737
738
739/**
740 * @brief SGD with momentum optimizer update (fp32 version)
741 *
742 * v_t = momentum * v_{t-1} + g_t
743 * w_t = w_{t-1} - lr * (v_t + weight_decay * w_{t-1})
744 *
745 * @param grad Gradient tensor (fp32) [numel]
746 * @param weight Weight tensor to update (fp32, in-place) [numel]
747 * @param velocity Velocity buffer (fp32, in-place) [numel]
748 * @param numel Number of elements
749 * @param lr Learning rate
750 * @param momentum Momentum coefficient (typically 0.9)
751 * @param weight_decay Weight decay coefficient
752 */
754 const float *grad,
755 float *weight,
756 float *velocity,
757 size_t numel,
758 float lr,
759 float momentum,
760 float weight_decay)
761{
762 if (!grad || !weight || !velocity || numel == 0) {
763 return;
764 }
765
766#if defined(__AVX512F__)
767 // AVX-512 path: process 16 floats at a time
768 __m512 v_lr = _mm512_set1_ps(lr);
769 __m512 v_momentum = _mm512_set1_ps(momentum);
770 __m512 v_weight_decay = _mm512_set1_ps(weight_decay);
771
772 size_t i = 0;
773 for (; i + 16 <= numel; i += 16) {
774 __m512 g = _mm512_loadu_ps(&grad[i]);
775 __m512 w = _mm512_loadu_ps(&weight[i]);
776 __m512 vel = _mm512_loadu_ps(&velocity[i]);
777
778 vel = _mm512_fmadd_ps(v_momentum, vel, g);
779 __m512 update = _mm512_fmadd_ps(v_weight_decay, w, vel);
780 w = _mm512_fnmadd_ps(v_lr, update, w);
781
782 _mm512_storeu_ps(&weight[i], w);
783 _mm512_storeu_ps(&velocity[i], vel);
784 }
785
786 for (; i < numel; ++i) {
787 velocity[i] = momentum * velocity[i] + grad[i];
788 weight[i] = weight[i] - lr * (velocity[i] + weight_decay * weight[i]);
789 }
790
791#elif defined(__AVX__)
792 // AVX path: process 8 floats at a time
793 __m256 v_lr = _mm256_set1_ps(lr);
794 __m256 v_momentum = _mm256_set1_ps(momentum);
795 __m256 v_weight_decay = _mm256_set1_ps(weight_decay);
796
797 size_t i = 0;
798 for (; i + 8 <= numel; i += 8) {
799 __m256 g = _mm256_loadu_ps(&grad[i]);
800 __m256 w = _mm256_loadu_ps(&weight[i]);
801 __m256 vel = _mm256_loadu_ps(&velocity[i]);
802
803 // v = momentum * v + g
804 vel = _mm256_add_ps(_mm256_mul_ps(v_momentum, vel), g);
805
806 // w = w - lr * (v + weight_decay * w)
807 __m256 update = _mm256_add_ps(vel, _mm256_mul_ps(v_weight_decay, w));
808 w = _mm256_sub_ps(w, _mm256_mul_ps(v_lr, update));
809
810 _mm256_storeu_ps(&weight[i], w);
811 _mm256_storeu_ps(&velocity[i], vel);
812 }
813
814 for (; i < numel; ++i) {
815 velocity[i] = momentum * velocity[i] + grad[i];
816 weight[i] = weight[i] - lr * (velocity[i] + weight_decay * weight[i]);
817 }
818
819#elif defined(__SSE2__)
820 // SSE2 path: process 4 floats at a time
821 __m128 v_lr = _mm_set1_ps(lr);
822 __m128 v_momentum = _mm_set1_ps(momentum);
823 __m128 v_weight_decay = _mm_set1_ps(weight_decay);
824
825 size_t i = 0;
826 for (; i + 4 <= numel; i += 4) {
827 __m128 g = _mm_loadu_ps(&grad[i]);
828 __m128 w = _mm_loadu_ps(&weight[i]);
829 __m128 vel = _mm_loadu_ps(&velocity[i]);
830
831 vel = _mm_add_ps(_mm_mul_ps(v_momentum, vel), g);
832 __m128 update = _mm_add_ps(vel, _mm_mul_ps(v_weight_decay, w));
833 w = _mm_sub_ps(w, _mm_mul_ps(v_lr, update));
834
835 _mm_storeu_ps(&weight[i], w);
836 _mm_storeu_ps(&velocity[i], vel);
837 }
838
839 for (; i < numel; ++i) {
840 velocity[i] = momentum * velocity[i] + grad[i];
841 weight[i] = weight[i] - lr * (velocity[i] + weight_decay * weight[i]);
842 }
843
844#else
845 // Scalar path
846 for (size_t i = 0; i < numel; ++i) {
847 velocity[i] = momentum * velocity[i] + grad[i];
848 weight[i] = weight[i] - lr * (velocity[i] + weight_decay * weight[i]);
849 }
850#endif
851}
852
853
854/**
855 * @brief Zero out gradient buffer (fp32)
856 *
857 * @param grad Gradient tensor to zero [numel]
858 * @param numel Number of elements
859 */
860void zero_gradients_f32(float *grad, size_t numel)
861{
862 if (!grad || numel == 0) {
863 return;
864 }
865 memset(grad, 0, numel * sizeof(float));
866}
867
868
869/**
870 * @brief Accumulate gradients: dst += src (fp32)
871 *
872 * Used for gradient accumulation across micro-batches.
873 *
874 * @param dst Destination gradient buffer (in-place) [numel]
875 * @param src Source gradient buffer [numel]
876 * @param numel Number of elements
877 */
878static void gradient_accumulate_f32_impl(float *dst, const float *src, size_t numel)
879{
880 if (!dst || !src || numel == 0) {
881 return;
882 }
883
884#if defined(__AVX512F__)
885 size_t i = 0;
886 for (; i + 16 <= numel; i += 16) {
887 __m512 d = _mm512_loadu_ps(&dst[i]);
888 __m512 s = _mm512_loadu_ps(&src[i]);
889 _mm512_storeu_ps(&dst[i], _mm512_add_ps(d, s));
890 }
891 for (; i < numel; ++i) {
892 dst[i] += src[i];
893 }
894
895#elif defined(__AVX__)
896 size_t i = 0;
897 for (; i + 8 <= numel; i += 8) {
898 __m256 d = _mm256_loadu_ps(&dst[i]);
899 __m256 s = _mm256_loadu_ps(&src[i]);
900 _mm256_storeu_ps(&dst[i], _mm256_add_ps(d, s));
901 }
902 for (; i < numel; ++i) {
903 dst[i] += src[i];
904 }
905
906#elif defined(__SSE2__)
907 size_t i = 0;
908 for (; i + 4 <= numel; i += 4) {
909 __m128 d = _mm_loadu_ps(&dst[i]);
910 __m128 s = _mm_loadu_ps(&src[i]);
911 _mm_storeu_ps(&dst[i], _mm_add_ps(d, s));
912 }
913 for (; i < numel; ++i) {
914 dst[i] += src[i];
915 }
916
917#else
918 for (size_t i = 0; i < numel; ++i) {
919 dst[i] += src[i];
920 }
921#endif
922}
923
924
925void gradient_accumulate_f32(float *dst, const float *src, size_t numel)
926{
927 if (!dst || !src || numel == 0) {
928 return;
929 }
930
931 ck_threadpool_t *pool = ck_threadpool_global();
932 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
933 if (!pool || nth <= 1 || nth > CK_OPT_PAR_MAX_THREADS || numel < CK_OPT_PAR_MIN_NUMEL) {
934 gradient_accumulate_f32_impl(dst, src, numel);
935 return;
936 }
937 int active_nth = ck_opt_pick_active_threads(nth, numel, CK_OPT_PAR_MIN_NUMEL);
938 if (active_nth <= 1) {
939 gradient_accumulate_f32_impl(dst, src, numel);
940 return;
941 }
942
943 ck_accum_parallel_args_t args = {
944 .dst = dst,
945 .src = src,
946 .numel = numel,
947 };
948 ck_threadpool_dispatch_n(pool, active_nth, ck_accum_parallel_work, &args);
949}
950
952 float *const *dsts,
953 const float *const *srcs,
954 const size_t *numels,
955 int tensor_count)
956{
957 if (!dsts || !srcs || !numels || tensor_count <= 0) {
958 return;
959 }
960
961 size_t total_numel = 0;
962 int valid_tensors = 0;
963 for (int i = 0; i < tensor_count; ++i) {
964 float *dst = dsts[i];
965 const float *src = srcs[i];
966 size_t n = numels[i];
967 if (!dst || !src || n == 0) {
968 continue;
969 }
970 total_numel += n;
971 valid_tensors += 1;
972 }
973 if (total_numel == 0 || valid_tensors == 0) {
974 return;
975 }
976
977 ck_threadpool_t *pool = ck_threadpool_global();
978 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
979 if (!pool || nth <= 1 || nth > CK_OPT_PAR_MAX_THREADS || total_numel < CK_OPT_PAR_MIN_NUMEL) {
980 for (int i = 0; i < tensor_count; ++i) {
981 float *dst = dsts[i];
982 const float *src = srcs[i];
983 size_t n = numels[i];
984 if (!dst || !src || n == 0) {
985 continue;
986 }
987 gradient_accumulate_f32_impl(dst, src, n);
988 }
989 return;
990 }
991
992 int active_nth = ck_opt_pick_active_threads(nth, total_numel, CK_OPT_PAR_MIN_NUMEL);
993 if (active_nth <= 1) {
994 for (int i = 0; i < tensor_count; ++i) {
995 float *dst = dsts[i];
996 const float *src = srcs[i];
997 size_t n = numels[i];
998 if (!dst || !src || n == 0) {
999 continue;
1000 }
1001 gradient_accumulate_f32_impl(dst, src, n);
1002 }
1003 return;
1004 }
1005
1006 ck_accum_multi_parallel_args_t args = {
1007 .dsts = dsts,
1008 .srcs = srcs,
1009 .numels = numels,
1010 .tensor_count = tensor_count,
1011 .total_numel = total_numel,
1012 };
1014}
1015
1016
1017/**
1018 * @brief Scale gradients by a constant: grad *= scale (fp32)
1019 *
1020 * Used for averaging gradients after accumulation: grad /= batch_size
1021 *
1022 * @param grad Gradient tensor to scale (in-place) [numel]
1023 * @param numel Number of elements
1024 * @param scale Scale factor (typically 1.0 / batch_size)
1025 */
1026static void gradient_scale_f32_impl(float *grad, size_t numel, float scale)
1027{
1028 if (!grad || numel == 0) {
1029 return;
1030 }
1031
1032#if defined(__AVX512F__)
1033 __m512 v_scale = _mm512_set1_ps(scale);
1034 size_t i = 0;
1035 for (; i + 16 <= numel; i += 16) {
1036 __m512 g = _mm512_loadu_ps(&grad[i]);
1037 _mm512_storeu_ps(&grad[i], _mm512_mul_ps(g, v_scale));
1038 }
1039 for (; i < numel; ++i) {
1040 grad[i] *= scale;
1041 }
1042
1043#elif defined(__AVX__)
1044 __m256 v_scale = _mm256_set1_ps(scale);
1045 size_t i = 0;
1046 for (; i + 8 <= numel; i += 8) {
1047 __m256 g = _mm256_loadu_ps(&grad[i]);
1048 _mm256_storeu_ps(&grad[i], _mm256_mul_ps(g, v_scale));
1049 }
1050 for (; i < numel; ++i) {
1051 grad[i] *= scale;
1052 }
1053
1054#elif defined(__SSE2__)
1055 __m128 v_scale = _mm_set1_ps(scale);
1056 size_t i = 0;
1057 for (; i + 4 <= numel; i += 4) {
1058 __m128 g = _mm_loadu_ps(&grad[i]);
1059 _mm_storeu_ps(&grad[i], _mm_mul_ps(g, v_scale));
1060 }
1061 for (; i < numel; ++i) {
1062 grad[i] *= scale;
1063 }
1064
1065#else
1066 for (size_t i = 0; i < numel; ++i) {
1067 grad[i] *= scale;
1068 }
1069#endif
1070}
1071
1072
1073void gradient_scale_f32(float *grad, size_t numel, float scale)
1074{
1075 if (!grad || numel == 0) {
1076 return;
1077 }
1078
1079 ck_threadpool_t *pool = ck_threadpool_global();
1080 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
1081 if (!pool || nth <= 1 || nth > CK_OPT_PAR_MAX_THREADS || numel < CK_OPT_PAR_MIN_NUMEL) {
1082 gradient_scale_f32_impl(grad, numel, scale);
1083 return;
1084 }
1085 int active_nth = ck_opt_pick_active_threads(nth, numel, CK_OPT_PAR_MIN_NUMEL);
1086 if (active_nth <= 1) {
1087 gradient_scale_f32_impl(grad, numel, scale);
1088 return;
1089 }
1090
1091 ck_scale_parallel_args_t args = {
1092 .grad = grad,
1093 .numel = numel,
1094 .scale = scale,
1095 };
1096 ck_threadpool_dispatch_n(pool, active_nth, ck_scale_parallel_work, &args);
1097}
1098
1099static double gradient_sum_sq_f32_impl(const float *grad, size_t numel)
1100{
1101 if (!grad || numel == 0) {
1102 return 0.0;
1103 }
1104
1105 double sum_sq = 0.0;
1106#if defined(__AVX512F__)
1107 __m512 acc = _mm512_setzero_ps();
1108 size_t i = 0;
1109 for (; i + 16 <= numel; i += 16) {
1110 __m512 g = _mm512_loadu_ps(&grad[i]);
1111 acc = _mm512_fmadd_ps(g, g, acc);
1112 }
1113 sum_sq = _mm512_reduce_add_ps(acc);
1114 for (; i < numel; ++i) {
1115 sum_sq += (double)grad[i] * (double)grad[i];
1116 }
1117#elif defined(__AVX__)
1118 __m256 acc = _mm256_setzero_ps();
1119 size_t i = 0;
1120 for (; i + 8 <= numel; i += 8) {
1121 __m256 g = _mm256_loadu_ps(&grad[i]);
1122 acc = _mm256_add_ps(acc, _mm256_mul_ps(g, g));
1123 }
1124 __m128 hi = _mm256_extractf128_ps(acc, 1);
1125 __m128 lo = _mm256_castps256_ps128(acc);
1126 __m128 sum4 = _mm_add_ps(lo, hi);
1127 __m128 shuf = _mm_movehdup_ps(sum4);
1128 __m128 sums = _mm_add_ps(sum4, shuf);
1129 shuf = _mm_movehl_ps(shuf, sums);
1130 sums = _mm_add_ss(sums, shuf);
1131 sum_sq = _mm_cvtss_f32(sums);
1132 for (; i < numel; ++i) {
1133 sum_sq += (double)grad[i] * (double)grad[i];
1134 }
1135#elif defined(__SSE2__)
1136 __m128 acc = _mm_setzero_ps();
1137 size_t i = 0;
1138 for (; i + 4 <= numel; i += 4) {
1139 __m128 g = _mm_loadu_ps(&grad[i]);
1140 acc = _mm_add_ps(acc, _mm_mul_ps(g, g));
1141 }
1142 __m128 shuf = _mm_shuffle_ps(acc, acc, _MM_SHUFFLE(2, 3, 0, 1));
1143 __m128 sums = _mm_add_ps(acc, shuf);
1144 shuf = _mm_movehl_ps(shuf, sums);
1145 sums = _mm_add_ss(sums, shuf);
1146 sum_sq = _mm_cvtss_f32(sums);
1147 for (; i < numel; ++i) {
1148 sum_sq += (double)grad[i] * (double)grad[i];
1149 }
1150#else
1151 for (size_t i = 0; i < numel; ++i) {
1152 sum_sq += (double)grad[i] * (double)grad[i];
1153 }
1154#endif
1155 return sum_sq;
1156}
1157
1158
1159/**
1160 * @brief Clip gradient norm (fp32)
1161 *
1162 * If ||grad||_2 > max_norm, scale grad so that ||grad||_2 = max_norm
1163 *
1164 * @param grad Gradient tensor to clip (in-place) [numel]
1165 * @param numel Number of elements
1166 * @param max_norm Maximum allowed L2 norm
1167 * @return The original L2 norm before clipping
1168 */
1169float gradient_clip_norm_f32(float *grad, size_t numel, float max_norm)
1170{
1171 if (!grad || numel == 0 || max_norm <= 0.0f) {
1172 return 0.0f;
1173 }
1174
1175 double sum_sq = 0.0;
1176 ck_threadpool_t *pool = ck_threadpool_global();
1177 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
1178
1179 if (pool && nth > 1 && nth <= CK_OPT_PAR_MAX_THREADS && numel >= CK_OPT_PAR_MIN_NUMEL) {
1180 int active_nth = ck_opt_pick_active_threads(nth, numel, CK_OPT_PAR_MIN_NUMEL);
1181 if (active_nth <= 1) {
1182 sum_sq = gradient_sum_sq_f32_impl(grad, numel);
1183 } else {
1184 ck_sum_sq_parallel_args_t args;
1185 args.grad = grad;
1186 args.numel = numel;
1187 for (int i = 0; i < CK_OPT_PAR_MAX_THREADS; ++i) {
1188 args.partial[i] = 0.0;
1189 }
1190 ck_threadpool_dispatch_n(pool, active_nth, ck_sum_sq_parallel_work, &args);
1191 for (int i = 0; i < active_nth; ++i) {
1192 sum_sq += args.partial[i];
1193 }
1194 }
1195 } else {
1196 sum_sq = gradient_sum_sq_f32_impl(grad, numel);
1197 }
1198
1199 float norm = sqrtf((float)sum_sq);
1200 if (norm > max_norm) {
1201 float scale = max_norm / norm;
1202 gradient_scale_f32(grad, numel, scale);
1203 }
1204 return norm;
1205}
1206
1207float gradient_global_norm_multi_f32(const float *const *grads, const size_t *numels, int tensor_count)
1208{
1209 if (!grads || !numels || tensor_count <= 0) {
1210 return 0.0f;
1211 }
1212
1213 size_t total_numel = 0;
1214 int valid_tensors = 0;
1215 for (int i = 0; i < tensor_count; ++i) {
1216 if (!grads[i] || numels[i] == 0) {
1217 continue;
1218 }
1219 total_numel += numels[i];
1220 valid_tensors += 1;
1221 }
1222 if (total_numel == 0 || valid_tensors == 0) {
1223 return 0.0f;
1224 }
1225
1226 double sum_sq = 0.0;
1227 ck_threadpool_t *pool = ck_threadpool_global();
1228 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
1229
1230 if (pool && nth > 1 && nth <= CK_OPT_PAR_MAX_THREADS &&
1231 total_numel >= CK_OPT_PAR_MIN_NUMEL && valid_tensors > 1) {
1232 int active_nth = ck_opt_pick_active_threads(nth, total_numel, CK_OPT_PAR_MIN_NUMEL);
1233 if (active_nth > valid_tensors) {
1234 active_nth = valid_tensors;
1235 }
1236 if (active_nth <= 1) {
1237 for (int i = 0; i < tensor_count; ++i) {
1238 const float *g = grads[i];
1239 size_t n = numels[i];
1240 if (!g || n == 0) {
1241 continue;
1242 }
1243 sum_sq += gradient_sum_sq_f32_impl(g, n);
1244 }
1245 } else {
1246 ck_sum_sq_multi_parallel_args_t args;
1247 args.grads = grads;
1248 args.numels = numels;
1249 args.tensor_count = tensor_count;
1250 for (int i = 0; i < CK_OPT_PAR_MAX_THREADS; ++i) {
1251 args.partial[i] = 0.0;
1252 }
1254 for (int i = 0; i < active_nth; ++i) {
1255 sum_sq += args.partial[i];
1256 }
1257 }
1258 } else {
1259 for (int i = 0; i < tensor_count; ++i) {
1260 const float *g = grads[i];
1261 size_t n = numels[i];
1262 if (!g || n == 0) {
1263 continue;
1264 }
1265 sum_sq += gradient_sum_sq_f32_impl(g, n);
1266 }
1267 }
1268
1269 if (!(sum_sq > 0.0)) {
1270 return 0.0f;
1271 }
1272 return sqrtf((float)sum_sq);
1273}
Persistent pthread thread pool for CK-Engine inference.
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)
int ck_threadpool_n_threads(const ck_threadpool_t *pool)
int ck_strict_parity_enabled(void)
static void ck_accum_multi_parallel_work(int ith, int nth, void *argp)
static void ck_sum_sq_multi_parallel_work(int ith, int nth, void *argp)
static void adamw_update_f32_impl(const float *grad, float *weight, float *m, float *v, size_t numel, float lr, float beta1, float beta2, float eps, float weight_decay, int step)
AdamW optimizer update (fp32 version)
void sgd_momentum_update_f32(const float *grad, float *weight, float *velocity, size_t numel, float lr, float momentum, float weight_decay)
SGD with momentum optimizer update (fp32 version)
static double gradient_sum_sq_f32_impl(const float *grad, size_t numel)
static void ck_scale_parallel_work(int ith, int nth, void *argp)
float gradient_clip_norm_f32(float *grad, size_t numel, float max_norm)
Clip gradient norm (fp32)
static int ck_opt_pick_active_threads(int nth, size_t work_items, size_t min_chunk)
#define CK_OPT_PAR_MIN_NUMEL
void gradient_accumulate_multi_f32(float *const *dsts, const float *const *srcs, const size_t *numels, int tensor_count)
static void ck_sum_sq_parallel_work(int ith, int nth, void *argp)
float gradient_global_norm_multi_f32(const float *const *grads, const size_t *numels, int tensor_count)
static void ck_adamw_multi_parallel_work(int ith, int nth, void *argp)
static void gradient_scale_f32_impl(float *grad, size_t numel, float scale)
Scale gradients by a constant: grad *= scale (fp32)
void adamw_clip_update_multi_f32(float *const *grads, float *const *weights, float *const *m_states, float *const *v_states, const size_t *numels, int tensor_count, float lr, float beta1, float beta2, float eps, float weight_decay, float max_grad_norm, int step)
void gradient_scale_f32(float *grad, size_t numel, float scale)
static void ck_adamw_parallel_work(int ith, int nth, void *argp)
void adamw_update_f32(const float *grad, float *weight, float *m, float *v, size_t numel, float lr, float beta1, float beta2, float eps, float weight_decay, int step)
void gradient_accumulate_f32(float *dst, const float *src, size_t numel)
#define CK_OPT_PAR_MAX_THREADS
void zero_gradients_f32(float *grad, size_t numel)
Zero out gradient buffer (fp32)
static void gradient_accumulate_f32_impl(float *dst, const float *src, size_t numel)
Accumulate gradients: dst += src (fp32)
static void ck_accum_parallel_work(int ith, int nth, void *argp)
uint32_t end
Definition utf8.c:215
uint32_t start
Definition utf8.c:214