← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
optimizer_kernels.c File Reference

Optimizer kernels for training (AdamW, SGD) More...

#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "ck_threadpool.h"
#include "ckernel_engine.h"

Go to the source code of this file.

Macros

#define CK_OPT_PAR_MAX_THREADS   256
 
#define CK_OPT_PAR_MIN_NUMEL   ((size_t)262144)
 

Functions

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 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)
 
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)
 
static void ck_accum_multi_parallel_work (int ith, int nth, void *argp)
 
static void ck_accum_parallel_work (int ith, int nth, void *argp)
 
static void ck_adamw_multi_parallel_work (int ith, int nth, void *argp)
 
static void ck_adamw_parallel_work (int ith, int nth, void *argp)
 
static int ck_opt_pick_active_threads (int nth, size_t work_items, size_t min_chunk)
 
static void ck_scale_parallel_work (int ith, int nth, void *argp)
 
static void ck_sum_sq_multi_parallel_work (int ith, int nth, void *argp)
 
static void ck_sum_sq_parallel_work (int ith, int nth, void *argp)
 
void gradient_accumulate_f32 (float *dst, const float *src, size_t numel)
 
static void gradient_accumulate_f32_impl (float *dst, const float *src, size_t numel)
 Accumulate gradients: dst += src (fp32)
 
void gradient_accumulate_multi_f32 (float *const *dsts, const float *const *srcs, const size_t *numels, int tensor_count)
 
float gradient_clip_norm_f32 (float *grad, size_t numel, float max_norm)
 Clip gradient norm (fp32)
 
float gradient_global_norm_multi_f32 (const float *const *grads, const size_t *numels, int tensor_count)
 
void gradient_scale_f32 (float *grad, size_t numel, float scale)
 
static void gradient_scale_f32_impl (float *grad, size_t numel, float scale)
 Scale gradients by a constant: grad *= scale (fp32)
 
static double gradient_sum_sq_f32_impl (const float *grad, size_t numel)
 
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)
 
void zero_gradients_f32 (float *grad, size_t numel)
 Zero out gradient buffer (fp32)
 

Detailed Description

Optimizer kernels for training (AdamW, SGD)

CK-ENGINE KERNEL RULES:

  1. NO malloc/free - memory via bump allocator, pointers passed in
  2. NO OpenMP - parallelization at orchestrator/codegen layer
  3. API must define: inputs, outputs, workspace, and memory layouts
  4. Pure computation - deterministic, no side effects

After changes: make test && make llamacpp-parity-full

AdamW Algorithm: m_t = beta1 * m_{t-1} + (1 - beta1) * g_t v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2 m_hat = m_t / (1 - beta1^t) v_hat = v_t / (1 - beta2^t) w_t = w_{t-1} - lr * (m_hat / (sqrt(v_hat) + eps) + weight_decay * w_{t-1})

Note: AdamW applies weight decay directly to weights, not to gradients. This is different from L2 regularization (Adam with L2 adds decay to gradient).

Epsilon amplification at early steps: at step 1 with bc2=0.001, elements where vā‰ˆ0 (sparse/near-zero gradients) produce sqrt(v_hat)+eps ā‰ˆ eps=1e-8, amplifying any fp32 rounding in the accumulated gradient by up to lr/eps = 1e6. This is expected AdamW behavior, not a bug. In parity tests, gate on mean_param_diff (not max_param_diff) for grad_accum > 1 to avoid false alarms from these outliers.

Long-horizon fp32 risk: at lr=1e-3 with all-fp32 SIMD paths, known drift begins around step ~800 due to accumulated rounding. Use ck_strict_parity_enabled() (fp64 path) for parity validation; for production training keep lr < 1e-3 or monitor.

Definition in file optimizer_kernels.c.

Macro Definition Documentation

◆ CK_OPT_PAR_MAX_THREADS

#define CK_OPT_PAR_MAX_THREADS   256

Definition at line 48 of file optimizer_kernels.c.

◆ CK_OPT_PAR_MIN_NUMEL

#define CK_OPT_PAR_MIN_NUMEL   ((size_t)262144)

Definition at line 47 of file optimizer_kernels.c.

Function Documentation

◆ adamw_clip_update_multi_f32()

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 
)

Definition at line 638 of file optimizer_kernels.c.

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}
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)
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)
static int ck_opt_pick_active_threads(int nth, size_t work_items, size_t min_chunk)
#define CK_OPT_PAR_MIN_NUMEL
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)
#define CK_OPT_PAR_MAX_THREADS

References adamw_update_f32_impl(), ck_adamw_multi_parallel_work(), CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_threadpool_dispatch_n(), ck_threadpool_global(), ck_threadpool_n_threads(), gradient_global_norm_multi_f32(), and gradient_scale_f32_impl().

◆ adamw_update_f32()

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 
)

Definition at line 592 of file optimizer_kernels.c.

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}
static void ck_adamw_parallel_work(int ith, int nth, void *argp)

References adamw_update_f32_impl(), ck_adamw_parallel_work(), CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_threadpool_dispatch_n(), ck_threadpool_global(), and ck_threadpool_n_threads().

◆ adamw_update_f32_impl()

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 
)
static

AdamW optimizer update (fp32 version)

Updates weights in-place using the AdamW algorithm. Momentum (m) and variance (v) are stored in fp32 for numerical stability.

Parameters
gradGradient tensor (fp32) [numel]
weightWeight tensor to update (fp32, in-place) [numel]
mFirst moment (momentum) buffer (fp32, in-place) [numel]
vSecond moment (variance) buffer (fp32, in-place) [numel]
numelNumber of elements
lrLearning rate
beta1Exponential decay rate for first moment (typically 0.9)
beta2Exponential decay rate for second moment (typically 0.999)
epsSmall constant for numerical stability (typically 1e-8)
weight_decayWeight decay coefficient (typically 0.01)
stepCurrent step number (1-indexed for bias correction)

Definition at line 355 of file optimizer_kernels.c.

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}
int ck_strict_parity_enabled(void)

References ck_strict_parity_enabled().

Referenced by adamw_clip_update_multi_f32(), adamw_update_f32(), ck_adamw_multi_parallel_work(), and ck_adamw_parallel_work().

◆ ck_accum_multi_parallel_work()

static void ck_accum_multi_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 192 of file optimizer_kernels.c.

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}
static void gradient_accumulate_f32_impl(float *dst, const float *src, size_t numel)
Accumulate gradients: dst += src (fp32)
uint32_t end
Definition utf8.c:215
uint32_t start
Definition utf8.c:214

References end, gradient_accumulate_f32_impl(), and start.

Referenced by gradient_accumulate_multi_f32().

◆ ck_accum_parallel_work()

static void ck_accum_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 174 of file optimizer_kernels.c.

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}

References end, gradient_accumulate_f32_impl(), and start.

Referenced by gradient_accumulate_f32().

◆ ck_adamw_multi_parallel_work()

static void ck_adamw_multi_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 289 of file optimizer_kernels.c.

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}

References adamw_update_f32_impl(), and gradient_scale_f32_impl().

Referenced by adamw_clip_update_multi_f32().

◆ ck_adamw_parallel_work()

static void ck_adamw_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 145 of file optimizer_kernels.c.

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}

References adamw_update_f32_impl(), end, and start.

Referenced by adamw_update_f32().

◆ ck_opt_pick_active_threads()

static int ck_opt_pick_active_threads ( int  nth,
size_t  work_items,
size_t  min_chunk 
)
static

Definition at line 130 of file optimizer_kernels.c.

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}

Referenced by adamw_clip_update_multi_f32(), adamw_update_f32(), gradient_accumulate_f32(), gradient_accumulate_multi_f32(), gradient_clip_norm_f32(), gradient_global_norm_multi_f32(), and gradient_scale_f32().

◆ ck_scale_parallel_work()

static void ck_scale_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 232 of file optimizer_kernels.c.

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}

References end, gradient_scale_f32_impl(), and start.

Referenced by gradient_scale_f32().

◆ ck_sum_sq_multi_parallel_work()

static void ck_sum_sq_multi_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 269 of file optimizer_kernels.c.

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}
static double gradient_sum_sq_f32_impl(const float *grad, size_t numel)

References CK_OPT_PAR_MAX_THREADS, and gradient_sum_sq_f32_impl().

Referenced by gradient_global_norm_multi_f32().

◆ ck_sum_sq_parallel_work()

static void ck_sum_sq_parallel_work ( int  ith,
int  nth,
void *  argp 
)
static

Definition at line 250 of file optimizer_kernels.c.

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}

References CK_OPT_PAR_MAX_THREADS, end, gradient_sum_sq_f32_impl(), and start.

Referenced by gradient_clip_norm_f32().

◆ gradient_accumulate_f32()

void gradient_accumulate_f32 ( float *  dst,
const float *  src,
size_t  numel 
)

Definition at line 925 of file optimizer_kernels.c.

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}
static void ck_accum_parallel_work(int ith, int nth, void *argp)

References ck_accum_parallel_work(), CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_threadpool_dispatch_n(), ck_threadpool_global(), ck_threadpool_n_threads(), and gradient_accumulate_f32_impl().

◆ gradient_accumulate_f32_impl()

static void gradient_accumulate_f32_impl ( float *  dst,
const float *  src,
size_t  numel 
)
static

Accumulate gradients: dst += src (fp32)

Used for gradient accumulation across micro-batches.

Parameters
dstDestination gradient buffer (in-place) [numel]
srcSource gradient buffer [numel]
numelNumber of elements

Definition at line 878 of file optimizer_kernels.c.

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}

Referenced by ck_accum_multi_parallel_work(), ck_accum_parallel_work(), gradient_accumulate_f32(), and gradient_accumulate_multi_f32().

◆ gradient_accumulate_multi_f32()

void gradient_accumulate_multi_f32 ( float *const *  dsts,
const float *const *  srcs,
const size_t *  numels,
int  tensor_count 
)

Definition at line 951 of file optimizer_kernels.c.

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}
static void ck_accum_multi_parallel_work(int ith, int nth, void *argp)

References ck_accum_multi_parallel_work(), CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_threadpool_dispatch_n(), ck_threadpool_global(), ck_threadpool_n_threads(), and gradient_accumulate_f32_impl().

◆ gradient_clip_norm_f32()

float gradient_clip_norm_f32 ( float *  grad,
size_t  numel,
float  max_norm 
)

Clip gradient norm (fp32)

If ||grad||_2 > max_norm, scale grad so that ||grad||_2 = max_norm

Parameters
gradGradient tensor to clip (in-place) [numel]
numelNumber of elements
max_normMaximum allowed L2 norm
Returns
The original L2 norm before clipping

Definition at line 1169 of file optimizer_kernels.c.

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}
static void ck_sum_sq_parallel_work(int ith, int nth, void *argp)
void gradient_scale_f32(float *grad, size_t numel, float scale)

References CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_sum_sq_parallel_work(), ck_threadpool_dispatch_n(), ck_threadpool_global(), ck_threadpool_n_threads(), gradient_scale_f32(), and gradient_sum_sq_f32_impl().

◆ gradient_global_norm_multi_f32()

float gradient_global_norm_multi_f32 ( const float *const *  grads,
const size_t *  numels,
int  tensor_count 
)

Definition at line 1207 of file optimizer_kernels.c.

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}
static void ck_sum_sq_multi_parallel_work(int ith, int nth, void *argp)

References CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_sum_sq_multi_parallel_work(), ck_threadpool_dispatch_n(), ck_threadpool_global(), ck_threadpool_n_threads(), and gradient_sum_sq_f32_impl().

Referenced by adamw_clip_update_multi_f32().

◆ gradient_scale_f32()

void gradient_scale_f32 ( float *  grad,
size_t  numel,
float  scale 
)

Definition at line 1073 of file optimizer_kernels.c.

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}
static void ck_scale_parallel_work(int ith, int nth, void *argp)

References CK_OPT_PAR_MAX_THREADS, CK_OPT_PAR_MIN_NUMEL, ck_opt_pick_active_threads(), ck_scale_parallel_work(), ck_threadpool_dispatch_n(), ck_threadpool_global(), ck_threadpool_n_threads(), and gradient_scale_f32_impl().

Referenced by gradient_clip_norm_f32().

◆ gradient_scale_f32_impl()

static void gradient_scale_f32_impl ( float *  grad,
size_t  numel,
float  scale 
)
static

Scale gradients by a constant: grad *= scale (fp32)

Used for averaging gradients after accumulation: grad /= batch_size

Parameters
gradGradient tensor to scale (in-place) [numel]
numelNumber of elements
scaleScale factor (typically 1.0 / batch_size)

Definition at line 1026 of file optimizer_kernels.c.

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}

Referenced by adamw_clip_update_multi_f32(), ck_adamw_multi_parallel_work(), ck_scale_parallel_work(), and gradient_scale_f32().

◆ gradient_sum_sq_f32_impl()

static double gradient_sum_sq_f32_impl ( const float *  grad,
size_t  numel 
)
static

Definition at line 1099 of file optimizer_kernels.c.

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}

Referenced by ck_sum_sq_multi_parallel_work(), ck_sum_sq_parallel_work(), gradient_clip_norm_f32(), and gradient_global_norm_multi_f32().

◆ sgd_momentum_update_f32()

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)

v_t = momentum * v_{t-1} + g_t w_t = w_{t-1} - lr * (v_t + weight_decay * w_{t-1})

Parameters
gradGradient tensor (fp32) [numel]
weightWeight tensor to update (fp32, in-place) [numel]
velocityVelocity buffer (fp32, in-place) [numel]
numelNumber of elements
lrLearning rate
momentumMomentum coefficient (typically 0.9)
weight_decayWeight decay coefficient

Definition at line 753 of file optimizer_kernels.c.

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}

◆ zero_gradients_f32()

void zero_gradients_f32 ( float *  grad,
size_t  numel 
)

Zero out gradient buffer (fp32)

Parameters
gradGradient tensor to zero [numel]
numelNumber of elements

Definition at line 860 of file optimizer_kernels.c.

861{
862 if (!grad || numel == 0) {
863 return;
864 }
865 memset(grad, 0, numel * sizeof(float));
866}