Deepity
Ultra-fast, SIMD-accelerated Predictive Coding
Loading...
Searching...
No Matches
Optimize.h
Go to the documentation of this file.
1#pragma once
2#include <algorithm>
3#include <cstddef>
4#include <memory>
5#include <vector>
6#include <immintrin.h>
7#include <omp.h>
8
9#ifdef __linux__
10#include <fstream>
11#elif defined(_WIN32)
12#include <windows.h>
13#endif
14
15#ifdef DEEPITY_USE_MKL
16#include <mkl_service.h>
17#else
18extern "C" {
21 void openblas_set_num_threads(int num_threads);
22}
23#endif
24
42namespace Deep
43{
46 inline size_t GetL2CacheBytes()
47 {
48#ifdef __linux__
49 std::ifstream f("/sys/devices/system/cpu/cpu0/cache/index2/size");
50 size_t kb = 0;
51 char unit;
52 f >> kb >> unit;
53 return kb * 1024;
54#elif defined(_WIN32)
55 DWORD bufSize = 0;
56 GetLogicalProcessorInformation(nullptr, &bufSize);
57
58 // Computed once, by name, instead of repeating `bufSize /
59 // sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)` inline at both the
60 // allocation site and the loop condition -- and std::vector instead
61 // of make_unique<T[]>(count), since that's the far more common,
62 // heavily-exercised pattern for a runtime-sized buffer like this.
63 const size_t count = static_cast<size_t>(bufSize) / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
64 std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buf(count);
65 GetLogicalProcessorInformation(buf.data(), &bufSize);
66
67 for (size_t i = 0; i < count; ++i)
68 {
69 if (buf[i].Relationship == RelationCache && buf[i].Cache.Level == 2)
70 return buf[i].Cache.Size;
71 }
72 return 512 * 1024; // fallback
73#else
74 return 512 * 1024; // fallback for unknown platforms
75#endif
76 }
77
83 inline size_t AutoBatchSize(size_t inSize, size_t outSize)
84 {
85 size_t l2 = GetL2CacheBytes();
86
87 size_t wSize = inSize * outSize * sizeof(float);
88 size_t bytesPerBatch = (outSize + inSize + inSize) * sizeof(float);
89
90 size_t remaining = (l2 > wSize) ? l2 - wSize : l2 / 2;
91 size_t B = remaining / bytesPerBatch;
92
93 // Round down to nearest power of 2
94 size_t pow2 = 1;
95 while (pow2 * 2 <= B)
96 pow2 *= 2;
97
98 if (pow2 < 64)
99 pow2 = 64;
100
101 if (pow2 > 512)
102 pow2 = 512;
103
104 return pow2;
105 }
106
114 static inline void DynamicThread(int batchSize) noexcept
115 {
116 static int currentThreads = -1;
117
118 const int THRESHOLD = 32;
119 int maxProcs = omp_get_num_procs();
120
121 // Confirmed via extended sweep (batch=1024/2048/4096, threads up to
122 // 48): the optimum drifts mildly upward with batch size (8 -> ~12 ->
123 // ~16) but the difference is small and noisy at this scale (~7%
124 // between 8/12/16 at batch=4096) -- NOT worth a hard per-batch-size
125 // table. What IS solid and unambiguous: 48 threads is always the
126 // worst choice tested, by 1.8-2.9x, at every batch size -- likely
127 // cross-CCD/Infinity Fabric synchronization cost on this many-core
128 // part. A smooth, capped scaling curve fits the real trend better
129 // than another single hardcoded number.
130 int scaled = 8 + (batchSize / 1024) * 4; // 8 @ <1024, ~12 @ 2048, ~16 @ 4096, etc.
131 const int MAX_USEFUL_THREADS = std::min(scaled, 16); // cap -- 48 was confirmed worse
132 // every time, no reason to extrapolate past 16
133
134 int targetThreads = (batchSize < THRESHOLD) ? 1 : std::min(maxProcs, MAX_USEFUL_THREADS);
135
136 if (currentThreads != targetThreads)
137 {
138 omp_set_num_threads(targetThreads);
139#ifdef DEEPITY_USE_MKL
140 mkl_set_num_threads(targetThreads);
141#else
142 openblas_set_num_threads(targetThreads);
143#endif
144 currentThreads = targetThreads;
145 }
146 }
147
149 static inline float hsum256_ps(__m256 x)
150 {
151 __m128 lo = _mm256_castps256_ps128(x);
152 __m128 hi = _mm256_extractf128_ps(x, 1);
153 lo = _mm_add_ps(lo, hi);
154
155 __m128 shuf = _mm_movehdup_ps(lo);
156 __m128 sums = _mm_add_ps(lo, shuf);
157 shuf = _mm_movehl_ps(shuf, sums);
158 sums = _mm_add_ss(sums, shuf);
159
160 return _mm_cvtss_f32(sums);
161 }
162
164 static inline float hsum128_ps(__m128 x)
165 {
166 __m128 shuf = _mm_movehdup_ps(x);
167 __m128 sums = _mm_add_ps(x, shuf);
168 shuf = _mm_movehl_ps(shuf, sums);
169 sums = _mm_add_ss(sums, shuf);
170 return _mm_cvtss_f32(sums);
171 }
172}
void openblas_set_num_threads(int num_threads)
OpenBLAS's own thread-count setter, declared directly since OpenBLAS ships no public header for it in...
This file exists to provide both Tensor.h and IComputeBackend.h with device types without introducing...
Definition Backend.cpp:10
static void DynamicThread(int batchSize) noexcept
Sets OpenMP's (and the BLAS library's) thread count for the given batch size, memoized so it's a no-o...
Definition Optimize.h:114
size_t GetL2CacheBytes()
Queries the size of this CPU's L2 cache (per-core, level 2), falling back to 512 KiB on platforms whe...
Definition Optimize.h:46
static float hsum128_ps(__m128 x)
Horizontal sum of all 4 lanes of an SSE register.
Definition Optimize.h:164
size_t AutoBatchSize(size_t inSize, size_t outSize)
Picks a batch size (a power of 2 clamped to [64, 512]) sized so a layer's weights plus one batch's ac...
Definition Optimize.h:83
static float hsum256_ps(__m256 x)
Horizontal sum of all 8 lanes of an AVX2 register.
Definition Optimize.h:149