Deepity
Ultra-fast, SIMD-accelerated Predictive Coding
Loading...
Searching...
No Matches
AdamOptimizer.h
Go to the documentation of this file.
1#pragma once
2#include <cstddef>
3#include <cmath>
4#include <immintrin.h>
5#include <sleef.h>
6#include <cstring>
9#if defined(_MSC_VER)
10#define RESTRICT __restrict
11#else
12#define RESTRICT __restrict__
13#endif
14
46
47namespace Deep
48{
51 enum class OptimizerType
52 {
56 };
57
64 inline OptimizerType toOpType(const char *s)
65 {
66 if (strncmp(s, "SGD", 3) == 0)
67 return OptimizerType::SGD;
68 else if (strncmp(s, "ADAM", 4) == 0)
70 else if (strncmp(s, "ADAMW", 5) == 0)
72 else
73 return OptimizerType::SGD;
74 }
75
107 inline void AdamWUpdate(float *RESTRICT w, const float *RESTRICT grad,
108 float *RESTRICT m, float *RESTRICT v,
109 size_t n, int t,
110 float lr, float lambda,
111 float beta1 = 0.9f, float beta2 = 0.999f, float eps = 1e-8f) noexcept
112 {
113 // Bias-correction terms depend only on t, beta1, beta2 -- identical
114 // for every element in this call. Computed ONCE (scalar), not
115 // per-element (would otherwise mean n redundant Sleef_powf calls
116 // for a value that never changes within this call).
117 const float beta1_t = 1.0f - Sleef_powf_u10(beta1, (float)t);
118 const float beta2_t = 1.0f - Sleef_powf_u10(beta2, (float)t);
119 const float inv_beta1_t = 1.0f / beta1_t;
120 const float inv_beta2_t = 1.0f / beta2_t;
121
122#if defined(__AVX512F__)
123 __m512 beta1_v = _mm512_set1_ps(beta1);
124 __m512 one_minus_beta1 = _mm512_set1_ps(1.0f - beta1);
125 __m512 beta2_v = _mm512_set1_ps(beta2);
126 __m512 one_minus_beta2 = _mm512_set1_ps(1.0f - beta2);
127 __m512 inv_beta1_t_v = _mm512_set1_ps(inv_beta1_t);
128 __m512 inv_beta2_t_v = _mm512_set1_ps(inv_beta2_t);
129 __m512 lr_v = _mm512_set1_ps(lr);
130 __m512 eps_v = _mm512_set1_ps(eps);
131 __m512 decay_v = _mm512_set1_ps(1.0f - lr * lambda); // AdamW decoupled decay factor
132
133 size_t r = n % 16;
134 size_t simd_end = n - r;
135#pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
136 for (ptrdiff_t i=0; i < (ptrdiff_t)simd_end; i += 16)
137 {
138 __m512 g = _mm512_loadu_ps(&grad[i]);
139 __m512 m_old = _mm512_loadu_ps(&m[i]);
140 __m512 v_old = _mm512_loadu_ps(&v[i]);
141
142 __m512 m_new = _mm512_fmadd_ps(beta1_v, m_old, _mm512_mul_ps(one_minus_beta1, g));
143 __m512 v_new = _mm512_fmadd_ps(beta2_v, v_old, _mm512_mul_ps(one_minus_beta2, _mm512_mul_ps(g, g)));
144
145 __m512 m_hat = _mm512_mul_ps(m_new, inv_beta1_t_v);
146 __m512 v_hat = _mm512_mul_ps(v_new, inv_beta2_t_v);
147
148 __m512 denom = _mm512_add_ps(_mm512_sqrt_ps(v_hat), eps_v);
149 __m512 step = _mm512_div_ps(_mm512_mul_ps(lr_v, m_hat), denom);
150
151 __m512 w_old = _mm512_loadu_ps(&w[i]);
152 // AdamW: decoupled decay applied to w directly, THEN the
153 // gradient-based step -- not folded into the gradient itself.
154 __m512 w_new = _mm512_sub_ps(_mm512_mul_ps(w_old, decay_v), step);
155
156 _mm512_storeu_ps(&w[i], w_new);
157 _mm512_storeu_ps(&m[i], m_new);
158 _mm512_storeu_ps(&v[i], v_new);
159 }
160#elif defined(__AVX2__) || defined(__AVX__)
161 __m256 beta1_v = _mm256_set1_ps(beta1);
162 __m256 one_minus_beta1 = _mm256_set1_ps(1.0f - beta1);
163 __m256 beta2_v = _mm256_set1_ps(beta2);
164 __m256 one_minus_beta2 = _mm256_set1_ps(1.0f - beta2);
165 __m256 inv_beta1_t_v = _mm256_set1_ps(inv_beta1_t);
166 __m256 inv_beta2_t_v = _mm256_set1_ps(inv_beta2_t);
167 __m256 lr_v = _mm256_set1_ps(lr);
168 __m256 eps_v = _mm256_set1_ps(eps);
169 __m256 decay_v = _mm256_set1_ps(1.0f - lr * lambda);
170
171 size_t r = n % 8;
172 size_t simd_end = n - r;
173#pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
174 for (ptrdiff_t i=0; i < (ptrdiff_t)simd_end; i += 8)
175 {
176 __m256 g = _mm256_loadu_ps(&grad[i]);
177 __m256 m_old = _mm256_loadu_ps(&m[i]);
178 __m256 v_old = _mm256_loadu_ps(&v[i]);
179
180#ifdef __FMA__
181 __m256 m_new = _mm256_fmadd_ps(beta1_v, m_old, _mm256_mul_ps(one_minus_beta1, g));
182 __m256 v_new = _mm256_fmadd_ps(beta2_v, v_old, _mm256_mul_ps(one_minus_beta2, _mm256_mul_ps(g, g)));
183#else
184 __m256 m_new = _mm256_add_ps(_mm256_mul_ps(beta1_v, m_old), _mm256_mul_ps(one_minus_beta1, g));
185 __m256 v_new = _mm256_add_ps(_mm256_mul_ps(beta2_v, v_old), _mm256_mul_ps(one_minus_beta2, _mm256_mul_ps(g, g)));
186#endif
187
188 __m256 m_hat = _mm256_mul_ps(m_new, inv_beta1_t_v);
189 __m256 v_hat = _mm256_mul_ps(v_new, inv_beta2_t_v);
190
191 __m256 denom = _mm256_add_ps(_mm256_sqrt_ps(v_hat), eps_v);
192 __m256 step = _mm256_div_ps(_mm256_mul_ps(lr_v, m_hat), denom);
193
194 __m256 w_old = _mm256_loadu_ps(&w[i]);
195 __m256 w_new = _mm256_sub_ps(_mm256_mul_ps(w_old, decay_v), step);
196
197 _mm256_storeu_ps(&w[i], w_new);
198 _mm256_storeu_ps(&m[i], m_new);
199 _mm256_storeu_ps(&v[i], v_new);
200 }
201#elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64)
202 __m128 beta1_v = _mm_set1_ps(beta1);
203 __m128 one_minus_beta1 = _mm_set1_ps(1.0f - beta1);
204 __m128 beta2_v = _mm_set1_ps(beta2);
205 __m128 one_minus_beta2 = _mm_set1_ps(1.0f - beta2);
206 __m128 inv_beta1_t_v = _mm_set1_ps(inv_beta1_t);
207 __m128 inv_beta2_t_v = _mm_set1_ps(inv_beta2_t);
208 __m128 lr_v = _mm_set1_ps(lr);
209 __m128 eps_v = _mm_set1_ps(eps);
210 __m128 decay_v = _mm_set1_ps(1.0f - lr * lambda);
211
212 size_t r = n % 4;
213 size_t simd_end = n - r;
214#pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
215 for (ptrdiff_t i=0; i < (ptrdiff_t)simd_end; i += 4)
216 {
217 __m128 g = _mm_loadu_ps(&grad[i]);
218 __m128 m_old = _mm_loadu_ps(&m[i]);
219 __m128 v_old = _mm_loadu_ps(&v[i]);
220
221#ifdef __FMA__
222 __m128 m_new = _mm_fmadd_ps(beta1_v, m_old, _mm_mul_ps(one_minus_beta1, g));
223 __m128 v_new = _mm_fmadd_ps(beta2_v, v_old, _mm_mul_ps(one_minus_beta2, _mm_mul_ps(g, g)));
224#else
225 __m128 m_new = _mm_add_ps(_mm_mul_ps(beta1_v, m_old), _mm_mul_ps(one_minus_beta1, g));
226 __m128 v_new = _mm_add_ps(_mm_mul_ps(beta2_v, v_old), _mm_mul_ps(one_minus_beta2, _mm_mul_ps(g, g)));
227#endif
228
229 __m128 m_hat = _mm_mul_ps(m_new, inv_beta1_t_v);
230 __m128 v_hat = _mm_mul_ps(v_new, inv_beta2_t_v);
231
232 __m128 denom = _mm_add_ps(_mm_sqrt_ps(v_hat), eps_v);
233 __m128 step = _mm_div_ps(_mm_mul_ps(lr_v, m_hat), denom);
234
235 __m128 w_old = _mm_loadu_ps(&w[i]);
236 __m128 w_new = _mm_sub_ps(_mm_mul_ps(w_old, decay_v), step);
237
238 _mm_storeu_ps(&w[i], w_new);
239 _mm_storeu_ps(&m[i], m_new);
240 _mm_storeu_ps(&v[i], v_new);
241 }
242#endif
243 // Scalar tail
244 float decay = 1.0f - lr * lambda;
245#if !defined(__AVX512F__) && !defined(__AVX2__) && !defined(__AVX__) && !defined(__SSE__) && !defined(_M_AMD64) && !defined(_M_X64)
246 #pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
247#endif
248 for (ptrdiff_t i=simd_end; i < (ptrdiff_t)n; ++i)
249 {
250 float g = grad[i];
251 m[i] = beta1 * m[i] + (1.0f - beta1) * g;
252 v[i] = beta2 * v[i] + (1.0f - beta2) * g * g;
253
254 float m_hat = m[i] * inv_beta1_t;
255 float v_hat = v[i] * inv_beta2_t;
256
257 w[i] = w[i] * decay - lr * m_hat / (Sleef_sqrtf_u05(v_hat) + eps);
258 }
259 }
260
295 inline void AdamUpdate(float *RESTRICT w, const float *RESTRICT grad,
296 float *RESTRICT m, float *RESTRICT v,
297 size_t n, int t,
298 float lr,
299 float beta1 = 0.9f, float beta2 = 0.999f, float eps = 1e-8f) noexcept
300 {
301 const float beta1_t = 1.0f - Sleef_powf_u10(beta1, (float)t);
302 const float beta2_t = 1.0f - Sleef_powf_u10(beta2, (float)t);
303 const float inv_beta1_t = 1.0f / beta1_t;
304 const float inv_beta2_t = 1.0f / beta2_t;
305
306#if defined(__AVX512F__)
307 __m512 beta1_v = _mm512_set1_ps(beta1);
308 __m512 one_minus_beta1 = _mm512_set1_ps(1.0f - beta1);
309 __m512 beta2_v = _mm512_set1_ps(beta2);
310 __m512 one_minus_beta2 = _mm512_set1_ps(1.0f - beta2);
311 __m512 inv_beta1_t_v = _mm512_set1_ps(inv_beta1_t);
312 __m512 inv_beta2_t_v = _mm512_set1_ps(inv_beta2_t);
313 __m512 lr_v = _mm512_set1_ps(lr);
314 __m512 eps_v = _mm512_set1_ps(eps);
315
316 size_t r = n % 16;
317 size_t simd_end = n - r;
318#pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
319 for (ptrdiff_t i=0; i < (ptrdiff_t)simd_end; i += 16)
320 {
321 __m512 g = _mm512_loadu_ps(&grad[i]);
322 __m512 m_old = _mm512_loadu_ps(&m[i]);
323 __m512 v_old = _mm512_loadu_ps(&v[i]);
324
325 __m512 m_new = _mm512_fmadd_ps(beta1_v, m_old, _mm512_mul_ps(one_minus_beta1, g));
326 __m512 v_new = _mm512_fmadd_ps(beta2_v, v_old, _mm512_mul_ps(one_minus_beta2, _mm512_mul_ps(g, g)));
327
328 __m512 m_hat = _mm512_mul_ps(m_new, inv_beta1_t_v);
329 __m512 v_hat = _mm512_mul_ps(v_new, inv_beta2_t_v);
330
331 __m512 denom = _mm512_add_ps(_mm512_sqrt_ps(v_hat), eps_v);
332 __m512 step = _mm512_div_ps(_mm512_mul_ps(lr_v, m_hat), denom);
333
334 __m512 w_old = _mm512_loadu_ps(&w[i]);
335 __m512 w_new = _mm512_sub_ps(w_old, step); // no decay term -- plain Adam
336
337 _mm512_storeu_ps(&w[i], w_new);
338 _mm512_storeu_ps(&m[i], m_new);
339 _mm512_storeu_ps(&v[i], v_new);
340 }
341#elif defined(__AVX2__) || defined(__AVX__)
342 __m256 beta1_v = _mm256_set1_ps(beta1);
343 __m256 one_minus_beta1 = _mm256_set1_ps(1.0f - beta1);
344 __m256 beta2_v = _mm256_set1_ps(beta2);
345 __m256 one_minus_beta2 = _mm256_set1_ps(1.0f - beta2);
346 __m256 inv_beta1_t_v = _mm256_set1_ps(inv_beta1_t);
347 __m256 inv_beta2_t_v = _mm256_set1_ps(inv_beta2_t);
348 __m256 lr_v = _mm256_set1_ps(lr);
349 __m256 eps_v = _mm256_set1_ps(eps);
350
351 size_t r = n % 8;
352 size_t simd_end = n - r;
353#pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
354 for (ptrdiff_t i=0; i < (ptrdiff_t)simd_end; i += 8)
355 {
356 __m256 g = _mm256_loadu_ps(&grad[i]);
357 __m256 m_old = _mm256_loadu_ps(&m[i]);
358 __m256 v_old = _mm256_loadu_ps(&v[i]);
359
360#ifdef __FMA__
361 __m256 m_new = _mm256_fmadd_ps(beta1_v, m_old, _mm256_mul_ps(one_minus_beta1, g));
362 __m256 v_new = _mm256_fmadd_ps(beta2_v, v_old, _mm256_mul_ps(one_minus_beta2, _mm256_mul_ps(g, g)));
363#else
364 __m256 m_new = _mm256_add_ps(_mm256_mul_ps(beta1_v, m_old), _mm256_mul_ps(one_minus_beta1, g));
365 __m256 v_new = _mm256_add_ps(_mm256_mul_ps(beta2_v, v_old), _mm256_mul_ps(one_minus_beta2, _mm256_mul_ps(g, g)));
366#endif
367
368 __m256 m_hat = _mm256_mul_ps(m_new, inv_beta1_t_v);
369 __m256 v_hat = _mm256_mul_ps(v_new, inv_beta2_t_v);
370
371 __m256 denom = _mm256_add_ps(_mm256_sqrt_ps(v_hat), eps_v);
372 __m256 step = _mm256_div_ps(_mm256_mul_ps(lr_v, m_hat), denom);
373
374 __m256 w_old = _mm256_loadu_ps(&w[i]);
375 __m256 w_new = _mm256_sub_ps(w_old, step);
376
377 _mm256_storeu_ps(&w[i], w_new);
378 _mm256_storeu_ps(&m[i], m_new);
379 _mm256_storeu_ps(&v[i], v_new);
380 }
381#elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64)
382 __m128 beta1_v = _mm_set1_ps(beta1);
383 __m128 one_minus_beta1 = _mm_set1_ps(1.0f - beta1);
384 __m128 beta2_v = _mm_set1_ps(beta2);
385 __m128 one_minus_beta2 = _mm_set1_ps(1.0f - beta2);
386 __m128 inv_beta1_t_v = _mm_set1_ps(inv_beta1_t);
387 __m128 inv_beta2_t_v = _mm_set1_ps(inv_beta2_t);
388 __m128 lr_v = _mm_set1_ps(lr);
389 __m128 eps_v = _mm_set1_ps(eps);
390
391 size_t r = n % 4;
392 size_t simd_end = n - r;
393#pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
394 for (ptrdiff_t i=0; i < (ptrdiff_t)simd_end; i += 4)
395 {
396 __m128 g = _mm_loadu_ps(&grad[i]);
397 __m128 m_old = _mm_loadu_ps(&m[i]);
398 __m128 v_old = _mm_loadu_ps(&v[i]);
399
400#ifdef __FMA__
401 __m128 m_new = _mm_fmadd_ps(beta1_v, m_old, _mm_mul_ps(one_minus_beta1, g));
402 __m128 v_new = _mm_fmadd_ps(beta2_v, v_old, _mm_mul_ps(one_minus_beta2, _mm_mul_ps(g, g)));
403#else
404 __m128 m_new = _mm_add_ps(_mm_mul_ps(beta1_v, m_old), _mm_mul_ps(one_minus_beta1, g));
405 __m128 v_new = _mm_add_ps(_mm_mul_ps(beta2_v, v_old), _mm_mul_ps(one_minus_beta2, _mm_mul_ps(g, g)));
406#endif
407
408 __m128 m_hat = _mm_mul_ps(m_new, inv_beta1_t_v);
409 __m128 v_hat = _mm_mul_ps(v_new, inv_beta2_t_v);
410
411 __m128 denom = _mm_add_ps(_mm_sqrt_ps(v_hat), eps_v);
412 __m128 step = _mm_div_ps(_mm_mul_ps(lr_v, m_hat), denom);
413
414 __m128 w_old = _mm_loadu_ps(&w[i]);
415 __m128 w_new = _mm_sub_ps(w_old, step);
416
417 _mm_storeu_ps(&w[i], w_new);
418 _mm_storeu_ps(&m[i], m_new);
419 _mm_storeu_ps(&v[i], v_new);
420 }
421#endif
422#if !defined(__AVX512F__) && !defined(__AVX2__) && !defined(__AVX__) && !defined(__SSE__) && !defined(_M_AMD64) && !defined(_M_X64)
423 #pragma omp parallel for schedule(static) if(n >= 4096 && !omp_in_parallel())
424#endif
425 for (ptrdiff_t i=simd_end; i < (ptrdiff_t)n; ++i)
426 {
427 float g = grad[i];
428 m[i] = beta1 * m[i] + (1.0f - beta1) * g;
429 v[i] = beta2 * v[i] + (1.0f - beta2) * g * g;
430
431 float m_hat = m[i] * inv_beta1_t;
432 float v_hat = v[i] * inv_beta2_t;
433
434 w[i] -= lr * m_hat / (Sleef_sqrtf_u05(v_hat) + eps);
435 }
436 }
437
438}
#define RESTRICT
Portability macro for the C++ restrict pointer qualifier (MSVC spells it differently from everyone el...
Definition ActivationMacros.h:16
This file exists to provide both Tensor.h and IComputeBackend.h with device types without introducing...
Definition Backend.cpp:10
void AdamWUpdate(float *RESTRICT w, const float *RESTRICT grad, float *RESTRICT m, float *RESTRICT v, size_t n, int t, float lr, float lambda, float beta1=0.9f, float beta2=0.999f, float eps=1e-8f) noexcept
Vectorized AdamW update over n elements. w, grad, m, v are all arrays of length n – ONE m/v pair PER ...
Definition AdamOptimizer.h:107
OptimizerType toOpType(const char *s)
Parses an OptimizerType from its name.
Definition AdamOptimizer.h:64
OptimizerType
Selects which weight-update rule a layer applies during UpdateWeights().
Definition AdamOptimizer.h:52
@ ADAM
Adam with no decoupled weight decay; see AdamUpdate().
Definition AdamOptimizer.h:54
@ SGD
Plain stochastic gradient descent (this codebase's existing cblas-based update).
Definition AdamOptimizer.h:53
@ ADAMW
Adam with decoupled weight decay; see AdamWUpdate().
Definition AdamOptimizer.h:55
void AdamUpdate(float *RESTRICT w, const float *RESTRICT grad, float *RESTRICT m, float *RESTRICT v, size_t n, int t, float lr, float beta1=0.9f, float beta2=0.999f, float eps=1e-8f) noexcept
Vectorized PLAIN Adam update over n elements – NO weight decay term at all, unlike AdamWUpdate above....
Definition AdamOptimizer.h:295