Skip to content

Commit 3e913ed

Browse files
committed
Vectorize SGD optimizer step, gradient merging, and gradient normalization loops using SIMD
1 parent 3f7ca70 commit 3e913ed

6 files changed

Lines changed: 196 additions & 107 deletions

File tree

include/neuralnetwork/common/simd_utils.h

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,5 +1364,157 @@ class simd
13641364
#endif
13651365
scalar_add_vectors(x, y, n, j);
13661366
}
1367+
1368+
// Scalar fallback for scale_vector
1369+
inline static void scalar_scale_vector(double* y, const double scale, size_t n, size_t start = 0) noexcept
1370+
{
1371+
for (size_t j = start; j < n; ++j)
1372+
{
1373+
y[j] *= scale;
1374+
}
1375+
}
1376+
1377+
// Vector-scalar multiplication (y *= scale)
1378+
inline static void scale_vector(double* y, const double scale, size_t n) noexcept
1379+
{
1380+
MYODDWEB_PROFILE_FUNCTION("simd");
1381+
size_t j = 0;
1382+
#ifdef SIMD_AVX2_ENABLED
1383+
__m256d vec_scale = _mm256_set1_pd(scale);
1384+
for (; j + 3 < n; j += 4)
1385+
{
1386+
__m256d vec_y = _mm256_loadu_pd(y + j);
1387+
vec_y = _mm256_mul_pd(vec_y, vec_scale);
1388+
_mm256_storeu_pd(y + j, vec_y);
1389+
}
1390+
#endif
1391+
scalar_scale_vector(y, scale, n, j);
1392+
}
1393+
1394+
// Scalar fallback for sgd_step
1395+
inline static void scalar_sgd_step(
1396+
double* values,
1397+
double* grads,
1398+
double* velocities,
1399+
const double* decays,
1400+
double momentum,
1401+
double lr,
1402+
double clipping_scale,
1403+
bool is_bias,
1404+
size_t n,
1405+
size_t start = 0) noexcept
1406+
{
1407+
for (size_t i = start; i < n; ++i)
1408+
{
1409+
double grad = grads[i] * clipping_scale;
1410+
if (!is_bias && decays != nullptr && decays[i] > 0.0)
1411+
{
1412+
grad += decays[i] * values[i];
1413+
}
1414+
double v = momentum * velocities[i] + grad;
1415+
values[i] -= lr * v;
1416+
velocities[i] = v;
1417+
grads[i] = grad;
1418+
}
1419+
}
1420+
1421+
// Vectorized SGD step
1422+
inline static void sgd_step(
1423+
double* values,
1424+
double* grads,
1425+
double* velocities,
1426+
const double* decays,
1427+
double momentum,
1428+
double lr,
1429+
double clipping_scale,
1430+
bool is_bias,
1431+
size_t n) noexcept
1432+
{
1433+
MYODDWEB_PROFILE_FUNCTION("simd");
1434+
size_t j = 0;
1435+
#ifdef SIMD_AVX2_ENABLED
1436+
__m256d vec_clip = _mm256_set1_pd(clipping_scale);
1437+
__m256d vec_momentum = _mm256_set1_pd(momentum);
1438+
__m256d vec_lr = _mm256_set1_pd(lr);
1439+
1440+
for (; j + 3 < n; j += 4)
1441+
{
1442+
__m256d g = _mm256_loadu_pd(&grads[j]);
1443+
__m256d cur_w = _mm256_loadu_pd(&values[j]);
1444+
__m256d cur_v = _mm256_loadu_pd(&velocities[j]);
1445+
1446+
__m256d grad = _mm256_mul_pd(g, vec_clip);
1447+
1448+
if (!is_bias && decays != nullptr)
1449+
{
1450+
__m256d d = _mm256_loadu_pd(&decays[j]);
1451+
#ifdef SIMD_FMA_ENABLED
1452+
grad = _mm256_fmadd_pd(d, cur_w, grad);
1453+
#else
1454+
grad = _mm256_add_pd(grad, _mm256_mul_pd(d, cur_w));
1455+
#endif
1456+
}
1457+
1458+
#ifdef SIMD_FMA_ENABLED
1459+
__m256d next_v = _mm256_fmadd_pd(vec_momentum, cur_v, grad);
1460+
#else
1461+
__m256d next_v = _mm256_add_pd(_mm256_mul_pd(vec_momentum, cur_v), grad);
1462+
#endif
1463+
1464+
__m256d next_w = _mm256_sub_pd(cur_w, _mm256_mul_pd(vec_lr, next_v));
1465+
1466+
_mm256_storeu_pd(&velocities[j], next_v);
1467+
_mm256_storeu_pd(&values[j], next_w);
1468+
_mm256_storeu_pd(&grads[j], grad);
1469+
}
1470+
#endif
1471+
scalar_sgd_step(values, grads, velocities, decays, momentum, lr, clipping_scale, is_bias, n, j);
1472+
}
1473+
1474+
// Scalar fallback for none_step
1475+
inline static void scalar_none_step(
1476+
double* values,
1477+
double* grads,
1478+
double lr,
1479+
double clipping_scale,
1480+
size_t n,
1481+
size_t start = 0) noexcept
1482+
{
1483+
for (size_t i = start; i < n; ++i)
1484+
{
1485+
double grad = grads[i] * clipping_scale;
1486+
values[i] -= lr * grad;
1487+
grads[i] = grad;
1488+
}
1489+
}
1490+
1491+
// Vectorized None step (plain SGD without momentum)
1492+
inline static void none_step(
1493+
double* values,
1494+
double* grads,
1495+
double lr,
1496+
double clipping_scale,
1497+
size_t n) noexcept
1498+
{
1499+
MYODDWEB_PROFILE_FUNCTION("simd");
1500+
size_t j = 0;
1501+
#ifdef SIMD_AVX2_ENABLED
1502+
__m256d vec_clip = _mm256_set1_pd(clipping_scale);
1503+
__m256d vec_lr = _mm256_set1_pd(lr);
1504+
1505+
for (; j + 3 < n; j += 4)
1506+
{
1507+
__m256d g = _mm256_loadu_pd(&grads[j]);
1508+
__m256d cur_w = _mm256_loadu_pd(&values[j]);
1509+
1510+
__m256d grad = _mm256_mul_pd(g, vec_clip);
1511+
__m256d next_w = _mm256_sub_pd(cur_w, _mm256_mul_pd(vec_lr, grad));
1512+
1513+
_mm256_storeu_pd(&values[j], next_w);
1514+
_mm256_storeu_pd(&grads[j], grad);
1515+
}
1516+
#endif
1517+
scalar_none_step(values, grads, lr, clipping_scale, n, j);
1518+
}
13671519
};
13681520
} // namespace myoddweb::nn

include/neuralnetwork/layers/elmanrnnlayer.cpp

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -875,38 +875,20 @@ void ElmanRNNLayer::calculate_and_store_gradients(const std::vector<GradientsAnd
875875
zero_gradients();
876876
for (unsigned int t = 0; t < num_threads; ++t)
877877
{
878-
for (size_t i = 0; i < _w_grads.size(); ++i)
879-
{
880-
_w_grads[i] += _thread_w_grads[t][i];
881-
}
882-
for (size_t i = 0; i < _rw_grads.size(); ++i)
883-
{
884-
_rw_grads[i] += _thread_rw_grads[t][i];
885-
}
878+
simd::add_vectors(_thread_w_grads[t].data(), _w_grads.data(), _w_grads.size());
879+
simd::add_vectors(_thread_rw_grads[t].data(), _rw_grads.data(), _rw_grads.size());
886880
if (has_bias())
887881
{
888-
for (size_t i = 0; i < _b_grads.size(); ++i)
889-
{
890-
_b_grads[i] += _thread_b_grads[t][i];
891-
}
882+
simd::add_vectors(_thread_b_grads[t].data(), _b_grads.data(), _b_grads.size());
892883
}
893884
}
894885

895886
const double inv_batch = 1.0 / static_cast<double>(batch_size);
896-
for (double& x : _w_grads)
897-
{
898-
x *= inv_batch;
899-
}
900-
for (double& x : _rw_grads)
901-
{
902-
x *= inv_batch;
903-
}
887+
simd::scale_vector(_w_grads.data(), inv_batch, _w_grads.size());
888+
simd::scale_vector(_rw_grads.data(), inv_batch, _rw_grads.size());
904889
if (has_bias())
905890
{
906-
for (double& x : _b_grads)
907-
{
908-
x *= inv_batch;
909-
}
891+
simd::scale_vector(_b_grads.data(), inv_batch, _b_grads.size());
910892
}
911893
}
912894

include/neuralnetwork/layers/fflayer.cpp

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -700,30 +700,18 @@ void FFLayer::calculate_and_store_gradients(const std::vector<GradientsAndOutput
700700

701701
for (unsigned int t = 0; t < num_threads; ++t)
702702
{
703-
for (size_t i = 0; i < _w_grads.size(); ++i)
704-
{
705-
_w_grads[i] += _thread_w_grads[t][i];
706-
}
703+
simd::add_vectors(_thread_w_grads[t].data(), _w_grads.data(), _w_grads.size());
707704
if (has_bias())
708705
{
709-
for (size_t i = 0; i < _b_grads.size(); ++i)
710-
{
711-
_b_grads[i] += _thread_b_grads[t][i];
712-
}
706+
simd::add_vectors(_thread_b_grads[t].data(), _b_grads.data(), _b_grads.size());
713707
}
714708
}
715709

716710
const double inv_batch = 1.0 / static_cast<double>(batch_size);
717-
for (double& grad : _w_grads)
718-
{
719-
grad *= inv_batch;
720-
}
711+
simd::scale_vector(_w_grads.data(), inv_batch, _w_grads.size());
721712
if (has_bias())
722713
{
723-
for (double& grad : _b_grads)
724-
{
725-
grad *= inv_batch;
726-
}
714+
simd::scale_vector(_b_grads.data(), inv_batch, _b_grads.size());
727715
}
728716
}
729717

include/neuralnetwork/layers/grurnnlayer.cpp

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,37 +1487,28 @@ void GRURNNLayer::calculate_and_store_gradients(
14871487
zero_gradients();
14881488
for (unsigned int t = 0; t < num_threads; ++t)
14891489
{
1490-
for (size_t i = 0; i < _w_grads.size(); ++i)
1491-
{
1492-
_w_grads[i] += _thread_w_grads[t][i];
1493-
_z_w_grads[i] += _thread_z_w_grads[t][i];
1494-
_r_w_grads[i] += _thread_r_w_grads[t][i];
1495-
}
1496-
for (size_t i = 0; i < _rw_grads.size(); ++i)
1497-
{
1498-
_rw_grads[i] += _thread_rw_grads[t][i];
1499-
_z_rw_grads[i] += _thread_z_rw_grads[t][i];
1500-
_r_rw_grads[i] += _thread_r_rw_grads[t][i];
1501-
}
1490+
simd::add_vectors(_thread_w_grads[t].data(), _w_grads.data(), _w_grads.size());
1491+
simd::add_vectors(_thread_z_w_grads[t].data(), _z_w_grads.data(), _z_w_grads.size());
1492+
simd::add_vectors(_thread_r_w_grads[t].data(), _r_w_grads.data(), _r_w_grads.size());
1493+
1494+
simd::add_vectors(_thread_rw_grads[t].data(), _rw_grads.data(), _rw_grads.size());
1495+
simd::add_vectors(_thread_z_rw_grads[t].data(), _z_rw_grads.data(), _z_rw_grads.size());
1496+
simd::add_vectors(_thread_r_rw_grads[t].data(), _r_rw_grads.data(), _r_rw_grads.size());
1497+
15021498
if (has_bias())
15031499
{
1504-
for (size_t i = 0; i < _b_grads.size(); ++i)
1505-
{
1506-
_b_grads[i] += _thread_b_grads[t][i];
1507-
_z_b_grads[i] += _thread_z_b_grads[t][i];
1508-
_r_b_grads[i] += _thread_r_b_grads[t][i];
1509-
}
1500+
simd::add_vectors(_thread_b_grads[t].data(), _b_grads.data(), _b_grads.size());
1501+
simd::add_vectors(_thread_z_b_grads[t].data(), _z_b_grads.data(), _z_b_grads.size());
1502+
simd::add_vectors(_thread_r_b_grads[t].data(), _r_b_grads.data(), _r_b_grads.size());
15101503
}
15111504
}
15121505

15131506
const double denom = static_cast<double>(batch_size);
1507+
const double inv_batch = 1.0 / denom;
15141508

1515-
const auto normalize = [&denom](std::vector<double>& grads)
1509+
const auto normalize = [inv_batch](std::vector<double>& grads)
15161510
{
1517-
for (double& g : grads)
1518-
{
1519-
g /= denom;
1520-
}
1511+
simd::scale_vector(grads.data(), inv_batch, grads.size());
15211512
};
15221513

15231514
normalize(_w_grads);

include/neuralnetwork/layers/layer.cpp

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -531,28 +531,14 @@ void Layer::apply_update_to_vector(
531531
switch (optimiser_type)
532532
{
533533
case OptimiserType::None:
534-
for (size_t i = 0; i < n; ++i)
535-
{
536-
double grad = grads[i] * clipping_scale;
537-
values[i] -= learning_rate * grad;
538-
grads[i] = grad;
539-
}
534+
simd::none_step(values.data(), grads.data(), learning_rate, clipping_scale, n);
540535
break;
541536

542537
case OptimiserType::SGD:
543538
{
544-
const auto& momentum = get_momentum();
545-
for (size_t i = 0; i < n; ++i)
546-
{
547-
double grad = grads[i] * clipping_scale;
548-
if (!is_bias && i < decays.size() && decays[i] > 0.0)
549-
{
550-
grad += decays[i] * values[i];
551-
}
552-
velocities[i] = momentum * velocities[i] + grad;
553-
values[i] -= learning_rate * velocities[i];
554-
grads[i] = grad;
555-
}
539+
const double momentum = get_momentum();
540+
const double* decay_ptr = (!is_bias && decays.size() >= n) ? decays.data() : nullptr;
541+
simd::sgd_step(values.data(), grads.data(), velocities.data(), decay_ptr, momentum, learning_rate, clipping_scale, is_bias, n);
556542
}
557543
break;
558544

include/neuralnetwork/layers/lstmlayer.cpp

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -952,36 +952,26 @@ const std::vector<GradientsAndOutputs>& batch_gradients_and_outputs, const std::
952952
zero_gradients();
953953
for (unsigned int t = 0; t < num_threads; ++t)
954954
{
955-
for (size_t i = 0; i < _w_grads.size(); ++i)
956-
{
957-
_w_grads[i] += _thread_w_grads[t][i];
958-
_f_w_grads[i] += _thread_f_w_grads[t][i];
959-
_i_w_grads[i] += _thread_i_w_grads[t][i];
960-
_o_w_grads[i] += _thread_o_w_grads[t][i];
961-
}
962-
for (size_t i = 0; i < _rw_grads.size(); ++i)
963-
{
964-
_rw_grads[i] += _thread_rw_grads[t][i];
965-
_f_rw_grads[i] += _thread_f_rw_grads[t][i];
966-
_i_rw_grads[i] += _thread_i_rw_grads[t][i];
967-
_o_rw_grads[i] += _thread_o_rw_grads[t][i];
968-
}
969-
for (size_t i = 0; i < _b_grads.size(); ++i)
970-
{
971-
_b_grads[i] += _thread_b_grads[t][i];
972-
_f_b_grads[i] += _thread_f_b_grads[t][i];
973-
_i_b_grads[i] += _thread_i_b_grads[t][i];
974-
_o_b_grads[i] += _thread_o_b_grads[t][i];
975-
}
955+
simd::add_vectors(_thread_w_grads[t].data(), _w_grads.data(), _w_grads.size());
956+
simd::add_vectors(_thread_f_w_grads[t].data(), _f_w_grads.data(), _f_w_grads.size());
957+
simd::add_vectors(_thread_i_w_grads[t].data(), _i_w_grads.data(), _i_w_grads.size());
958+
simd::add_vectors(_thread_o_w_grads[t].data(), _o_w_grads.data(), _o_w_grads.size());
959+
960+
simd::add_vectors(_thread_rw_grads[t].data(), _rw_grads.data(), _rw_grads.size());
961+
simd::add_vectors(_thread_f_rw_grads[t].data(), _f_rw_grads.data(), _f_rw_grads.size());
962+
simd::add_vectors(_thread_i_rw_grads[t].data(), _i_rw_grads.data(), _i_rw_grads.size());
963+
simd::add_vectors(_thread_o_rw_grads[t].data(), _o_rw_grads.data(), _o_rw_grads.size());
964+
965+
simd::add_vectors(_thread_b_grads[t].data(), _b_grads.data(), _b_grads.size());
966+
simd::add_vectors(_thread_f_b_grads[t].data(), _f_b_grads.data(), _f_b_grads.size());
967+
simd::add_vectors(_thread_i_b_grads[t].data(), _i_b_grads.data(), _i_b_grads.size());
968+
simd::add_vectors(_thread_o_b_grads[t].data(), _o_b_grads.data(), _o_b_grads.size());
976969
}
977970

978971
const double inv_batch = 1.0 / static_cast<double>(batch_size);
979-
auto norm = [&inv_batch](std::vector<double>& v)
972+
auto norm = [inv_batch](std::vector<double>& v)
980973
{
981-
for (double& x : v)
982-
{
983-
x *= inv_batch;
984-
}
974+
simd::scale_vector(v.data(), inv_batch, v.size());
985975
};
986976
norm(_w_grads);
987977
norm(_b_grads);

0 commit comments

Comments
 (0)