BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
BmnGemStripMLPModel.cxx
Go to the documentation of this file.
2
3#include "FairLogger.h"
4#include "TString.h"
5#include "TSystem.h"
6
7#include <algorithm>
8#include <cmath>
9#include <cstdint>
10#include <cstring>
11#include <fstream>
12#include <limits>
13#include <map>
14#include <mutex>
15#include <set>
16#include <sstream>
17
18// Model file format (.bmnmlp, little-endian) ----------------------------------
19// Written by Converter_KerasToBmnMLP.py, the version must be the same in both.
20//
21// char[8] magic "BMNMLP\0\0"
22// u32 format version (= kFormatVersion)
23// u32 architecture type (1 - residual MLP)
24// u32 input_dim, width, n_blocks, output_dim
25// u32 output activation (0 - linear, 1 - sigmoid)
26// f32 LayerNormalization epsilon
27// f32 LeakyReLU negative slope
28// str arch_hash, source (.keras file name), created (date) str = u32 length + bytes
29// u32 n_features, then n_features x str (feature names, the input order)
30// u32 n_params, then n_params x f32:
31// W_in[input_dim][width], b_in[width],
32// n_blocks x { ln_gamma[width], ln_beta[width], W1[width][width], b1[width], W2[width][width], b2[width]
33// }, ln_gamma[width], ln_beta[width], W_out[width][output_dim], b_out[output_dim]
34// u32 n_tests
35// f32 test tolerance (max absolute difference)
36// f32 n_tests x input_dim (test inputs)
37// f32 n_tests x output_dim (Keras outputs)
38// u32 CRC32 of all preceding bytes
39//
40// Dense kernels are stored as in Keras: [in][out] (row of the input i = weights to all outputs).
41//------------------------------------------------------------------------------
42
43namespace
44{
45const char kMagic[8] = {'B', 'M', 'N', 'M', 'L', 'P', '\0', '\0'};
46const uint32_t kFormatVersion = 1;
47const uint32_t kArchResidualMLP = 1;
48const char* const kBuiltinDefaultPath = "$VMCWORKDIR/parameters/gem/ANNModels/GEM_DefaultModel.bmnmlp";
49
50// CRC32 (the same as zlib.crc32 in Python) ------------------------------------
51uint32_t Crc32(const unsigned char* data, size_t n)
52{
53 static uint32_t table[256];
54 static std::once_flag table_flag;
55 std::call_once(table_flag, [] {
56 for (uint32_t i = 0; i < 256; ++i) {
57 uint32_t c = i;
58 for (int k = 0; k < 8; ++k)
59 c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
60 table[i] = c;
61 }
62 });
63
64 uint32_t crc = 0xFFFFFFFFu;
65 for (size_t i = 0; i < n; ++i)
66 crc = table[(crc ^ data[i]) & 0xFFu] ^ (crc >> 8);
67 return crc ^ 0xFFFFFFFFu;
68}
69
70bool IsLittleEndian()
71{
72 const uint32_t one = 1;
73 unsigned char first;
74 std::memcpy(&first, &one, 1);
75 return first == 1;
76}
77
78// Reading the buffer with bounds checks ---------------------------------------
79class Reader
80{
81 public:
82 Reader(const unsigned char* data, size_t size)
83 : fData(data)
84 , fSize(size)
85 , fPos(0)
86 {}
87
88 bool Bytes(void* dst, size_t n)
89 {
90 if (n > fSize - fPos)
91 return false;
92 std::memcpy(dst, fData + fPos, n);
93 fPos += n;
94 return true;
95 }
96 bool U32(uint32_t& v) { return Bytes(&v, sizeof(v)); }
97 bool F32(float& v) { return Bytes(&v, sizeof(v)); }
98 bool Floats(std::vector<float>& v, size_t n)
99 {
100 if (n > (fSize - fPos) / sizeof(float))
101 return false;
102 v.resize(n);
103 return n == 0 || Bytes(v.data(), n * sizeof(float));
104 }
105 bool Str(std::string& s)
106 {
107 uint32_t len = 0;
108 if (!U32(len) || len > (1u << 20) || len > fSize - fPos)
109 return false;
110 s.assign(reinterpret_cast<const char*>(fData + fPos), len);
111 fPos += len;
112 return true;
113 }
114 size_t Pos() const { return fPos; }
115
116 private:
117 const unsigned char* fData;
118 size_t fSize;
119 size_t fPos;
120};
121
122// Kernels ---------------------------------------------------------------------
123// y[j] += sum_k xs[k] * W[row_k][j], W[n_in][n_out] (Keras layout), row_k = rows[k] (kIndexed) or k.
124// Explicit 4-float vectors (GCC/Clang vector extensions, SSE2/NEON baseline): the same speed at -O2 and -O3,
125// without -march flags. 8 accumulators = 32 outputs in registers, rows of W are read sequentially.
126// For each output the sum goes in the order of k, so PredictSplit() gives exactly the same result as Predict().
127#if defined(__GNUC__)
128typedef float v4f __attribute__((vector_size(16)));
129
130inline v4f Load4(const float* p)
131{
132 v4f v;
133 std::memcpy(&v, p, sizeof(v));
134 return v;
135}
136
137inline void Store4(float* p, v4f v)
138{
139 std::memcpy(p, &v, sizeof(v));
140}
141#endif
142
143template<bool kIndexed>
144inline void AccumulateRows(float* __restrict y,
145 int n_out,
146 const float* __restrict W,
147 const float* __restrict xs,
148 const int* __restrict rows,
149 int n_rows)
150{
151 int j = 0;
152#if defined(__GNUC__)
153 for (; j + 32 <= n_out; j += 32) {
154 v4f a0 = Load4(y + j), a1 = Load4(y + j + 4), a2 = Load4(y + j + 8), a3 = Load4(y + j + 12);
155 v4f a4 = Load4(y + j + 16), a5 = Load4(y + j + 20), a6 = Load4(y + j + 24), a7 = Load4(y + j + 28);
156 for (int k = 0; k < n_rows; ++k) {
157 const float* w = W + static_cast<size_t>(kIndexed ? rows[k] : k) * n_out + j;
158 const float s = xs[k];
159 const v4f b = {s, s, s, s};
160 a0 += b * Load4(w);
161 a1 += b * Load4(w + 4);
162 a2 += b * Load4(w + 8);
163 a3 += b * Load4(w + 12);
164 a4 += b * Load4(w + 16);
165 a5 += b * Load4(w + 20);
166 a6 += b * Load4(w + 24);
167 a7 += b * Load4(w + 28);
168 }
169 Store4(y + j, a0);
170 Store4(y + j + 4, a1);
171 Store4(y + j + 8, a2);
172 Store4(y + j + 12, a3);
173 Store4(y + j + 16, a4);
174 Store4(y + j + 20, a5);
175 Store4(y + j + 24, a6);
176 Store4(y + j + 28, a7);
177 }
178 for (; j + 4 <= n_out; j += 4) {
179 v4f a0 = Load4(y + j);
180 for (int k = 0; k < n_rows; ++k) {
181 const float s = xs[k];
182 const v4f b = {s, s, s, s};
183 a0 += b * Load4(W + static_cast<size_t>(kIndexed ? rows[k] : k) * n_out + j);
184 }
185 Store4(y + j, a0);
186 }
187#endif
188 for (; j < n_out; ++j) {
189 float s = y[j];
190 for (int k = 0; k < n_rows; ++k)
191 s += xs[k] * W[static_cast<size_t>(kIndexed ? rows[k] : k) * n_out + j];
192 y[j] = s;
193 }
194}
195
196// Non-zero inputs (the segment features are often 0): x[0..n) -> rows = offset + i, vals = x[i]; returns the count
197inline int CompactNonZero(const float* x, int n, int offset, int* rows, float* vals)
198{
199 int nnz = 0;
200 for (int i = 0; i < n; ++i) {
201 if (x[i] != 0.0f) {
202 rows[nnz] = offset + i;
203 vals[nnz] = x[i];
204 ++nnz;
205 }
206 }
207 return nnz;
208}
209
210// y = b + x * W, W[n_in][n_out]
211inline void Dense(const float* __restrict x,
212 int n_in,
213 const float* __restrict W,
214 const float* __restrict b,
215 int n_out,
216 float* __restrict y)
217{
218 std::memcpy(y, b, sizeof(float) * n_out);
219 AccumulateRows<false>(y, n_out, W, x, nullptr, n_in);
220}
221
222inline void LeakyReLU(float* x, int n, float slope)
223{
224 for (int i = 0; i < n; ++i)
225 x[i] = (x[i] > 0.0f) ? x[i] : slope * x[i];
226}
227
228// y = (x - mean) / sqrt(var + eps) * gamma + beta (over the whole vector, biased variance as in Keras)
229inline void LayerNorm(const float* __restrict x,
230 int n,
231 const float* __restrict gamma,
232 const float* __restrict beta,
233 float eps,
234 float* __restrict y)
235{
236 double sum = 0.0;
237 for (int i = 0; i < n; ++i)
238 sum += x[i];
239 const double mean = sum / n;
240 double sum2 = 0.0;
241 for (int i = 0; i < n; ++i) {
242 const double d = x[i] - mean;
243 sum2 += d * d;
244 }
245 const double var = sum2 / n;
246 const float inv = static_cast<float>(1.0 / std::sqrt(var + eps));
247 const float fmean = static_cast<float>(mean);
248 for (int i = 0; i < n; ++i)
249 y[i] = (x[i] - fmean) * inv * gamma[i] + beta[i];
250}
251
252// Path with environment variables ($VMCWORKDIR, ~, ...) -> real path (ROOT: gSystem->ExpandPathName).
253// ok = false if it cannot be expanded (an environment variable is not set); the path is returned as given then.
254std::string ExpandPathName(const std::string& path, bool* ok = nullptr)
255{
256 TString expanded(path.c_str());
257 const bool failed = gSystem->ExpandPathName(expanded); // kTRUE in case of error
258 if (ok)
259 *ok = !failed;
260 return failed ? path : std::string(expanded.Data());
261}
262
263// Shared models ---------------------------------------------------------------
264struct SharedState
265{
266 std::mutex mutex;
267 std::string default_path = kBuiltinDefaultPath;
268 std::map<std::string, std::shared_ptr<const BmnGemStripMLPModel>> models; // key - expanded path
269 std::set<std::string> failed; // paths that failed to load
270 std::map<std::string, std::string> expanded; // path as given -> expanded path
271};
272
273SharedState& Shared()
274{
275 static SharedState state;
276 return state;
277}
278//==============================================================================
279} // namespace
280//------------------------------------------------------------------------------
281
286
287BmnGemStripMLPModel::BmnGemStripMLPModel(const std::string& path, bool verbose)
288{
289 Clear();
290 Load(path, verbose);
291}
292
294
296{
297 fLoaded = false;
298
299 fInputDim = 0;
300 fWidth = 0;
301 fNBlocks = 0;
302 fOutputDim = 0;
304 fLayerNormEps = 0.0f;
305 fLeakyReLUSlope = 0.0f;
306
307 fFilePath.clear();
308 fArchHash.clear();
309 fSourceName.clear();
310 fCreated.clear();
311 fFeatureNames.clear();
312
313 std::vector<float>().swap(fParams);
316
317 fNTests = 0;
318 fTestTolerance = 0.0f;
319 std::vector<float>().swap(fTestInputs);
320 std::vector<float>().swap(fTestOutputs);
321}
322//------------------------------------------------------------------------------
323
324bool BmnGemStripMLPModel::Load(const std::string& path, bool verbose)
325{
326 Clear();
327
328 bool expanded_ok = true;
329 const std::string full_path = ExpandPathName(path, &expanded_ok);
330
331 auto fail = [&](const std::string& reason) {
332 if (verbose)
333 LOG(error) << "BmnGemStripMLPModel: cannot load model " << full_path << ": " << reason;
334 Clear();
335 return false;
336 };
337
338 if (!IsLittleEndian())
339 return fail("only little-endian platforms are supported");
340
341 std::ifstream file(full_path, std::ios::binary | std::ios::ate);
342 if (!file) {
343 std::string reason = "file not found or not readable";
344 if (!expanded_ok)
345 reason += " (an environment variable in the path is not set)";
346 return fail(reason);
347 }
348
349 const std::streamsize size = file.tellg();
350 if (size <= 0)
351 return fail("empty file");
352 std::vector<unsigned char> buffer(static_cast<size_t>(size));
353 file.seekg(0);
354 if (!file.read(reinterpret_cast<char*>(buffer.data()), size))
355 return fail("read error");
356
357 std::string error;
358 if (!Parse(buffer, error))
359 return fail(error);
360
361 fFilePath = full_path;
362 fLoaded = true;
363
364 // Self-test: the same results as Keras on the test samples from the file
365 float max_diff = 0.0f;
366 if (!RunSelfTest(&max_diff, false)) {
367 std::ostringstream reason;
368 reason << "self-test failed: max |output - Keras| = " << max_diff << " > tolerance " << fTestTolerance;
369 return fail(reason.str());
370 }
371
372 return true;
373}
374//------------------------------------------------------------------------------
375
376bool BmnGemStripMLPModel::Parse(const std::vector<unsigned char>& buffer, std::string& error)
377{
378 // Checksum: the last 4 bytes - CRC32 of everything before them
379 if (buffer.size() < sizeof(kMagic) + 4 * sizeof(uint32_t)) {
380 error = "file is too short";
381 return false;
382 }
383 uint32_t crc_stored = 0;
384 std::memcpy(&crc_stored, buffer.data() + buffer.size() - sizeof(uint32_t), sizeof(uint32_t));
385 const size_t data_size = buffer.size() - sizeof(uint32_t);
386 if (Crc32(buffer.data(), data_size) != crc_stored) {
387 error = "checksum mismatch (the file is damaged or truncated)";
388 return false;
389 }
390
391 Reader rd(buffer.data(), data_size);
392 auto bad = [&](const std::string& what) {
393 error = "format error: " + what;
394 return false;
395 };
396
397 char magic[sizeof(kMagic)];
398 if (!rd.Bytes(magic, sizeof(magic)) || std::memcmp(magic, kMagic, sizeof(kMagic)) != 0)
399 return bad("not a .bmnmlp file (wrong signature)");
400
401 uint32_t version = 0, arch = 0;
402 if (!rd.U32(version))
403 return bad("no version");
404 if (version != kFormatVersion) {
405 error = "unsupported format version " + std::to_string(version)
406 + " (supported: " + std::to_string(kFormatVersion) + ")";
407 return false;
408 }
409 if (!rd.U32(arch))
410 return bad("no architecture type");
411 if (arch != kArchResidualMLP) {
412 error = "unsupported architecture type " + std::to_string(arch);
413 return false;
414 }
415
416 uint32_t input_dim = 0, width = 0, n_blocks = 0, output_dim = 0, out_act = 0;
417 float eps = 0.0f, slope = 0.0f;
418 if (!rd.U32(input_dim) || !rd.U32(width) || !rd.U32(n_blocks) || !rd.U32(output_dim) || !rd.U32(out_act)
419 || !rd.F32(eps) || !rd.F32(slope))
420 return bad("truncated header");
421
422 if (input_dim < 1 || input_dim > static_cast<uint32_t>(kMaxDim))
423 return bad("input_dim = " + std::to_string(input_dim) + " is out of range");
424 if (output_dim < 1 || output_dim > static_cast<uint32_t>(kMaxDim))
425 return bad("output_dim = " + std::to_string(output_dim) + " is out of range");
426 if (width < 1 || width > static_cast<uint32_t>(kMaxWidth))
427 return bad("width = " + std::to_string(width) + " is out of range (max " + std::to_string(kMaxWidth) + ")");
428 if (n_blocks > 10000)
429 return bad("n_blocks = " + std::to_string(n_blocks) + " is out of range");
430 if (out_act != kLinearOutput && out_act != kSigmoidOutput)
431 return bad("unknown output activation " + std::to_string(out_act));
432 if (!(eps > 0.0f) || !std::isfinite(eps) || !std::isfinite(slope))
433 return bad("bad LayerNorm epsilon or LeakyReLU slope");
434
435 std::string arch_hash, source, created;
436 if (!rd.Str(arch_hash) || !rd.Str(source) || !rd.Str(created))
437 return bad("truncated strings");
438
439 uint32_t n_features = 0;
440 if (!rd.U32(n_features))
441 return bad("no feature count");
442 if (n_features != input_dim)
443 return bad("number of feature names (" + std::to_string(n_features) + ") != input_dim ("
444 + std::to_string(input_dim) + ")");
445 std::vector<std::string> names(n_features);
446 for (auto& name : names)
447 if (!rd.Str(name))
448 return bad("truncated feature names");
449
450 // Weights
451 const size_t w = width, in = input_dim, out = output_dim;
452 const size_t block_size = 2 * w + 2 * (w * w + w);
453 const size_t n_expected = in * w + w + n_blocks * block_size + 2 * w + w * out + out;
454
455 uint32_t n_params = 0;
456 if (!rd.U32(n_params))
457 return bad("no parameter count");
458 if (n_params != n_expected)
459 return bad("number of parameters " + std::to_string(n_params) + ", expected " + std::to_string(n_expected));
460 std::vector<float> params;
461 if (!rd.Floats(params, n_params))
462 return bad("truncated weights");
463 for (float v : params)
464 if (!std::isfinite(v))
465 return bad("NaN or Inf in the weights");
466
467 // Tests
468 uint32_t n_tests = 0;
469 float tolerance = 0.0f;
470 if (!rd.U32(n_tests) || !rd.F32(tolerance))
471 return bad("truncated test header");
472 if (n_tests > 100000)
473 return bad("too many test samples");
474 std::vector<float> test_in, test_out;
475 if (!rd.Floats(test_in, static_cast<size_t>(n_tests) * in)
476 || !rd.Floats(test_out, static_cast<size_t>(n_tests) * out))
477 return bad("truncated test samples");
478
479 if (rd.Pos() != data_size)
480 return bad("unexpected data at the end of the file");
481
482 // Everything is read: fill the model
483 fInputDim = static_cast<int>(input_dim);
484 fWidth = static_cast<int>(width);
485 fNBlocks = static_cast<int>(n_blocks);
486 fOutputDim = static_cast<int>(output_dim);
487 fOutputActivation = static_cast<int>(out_act);
488 fLayerNormEps = eps;
489 fLeakyReLUSlope = slope;
490 fArchHash = arch_hash;
491 fSourceName = source;
492 fCreated = created;
493 fFeatureNames = std::move(names);
494 fParams = std::move(params);
495
496 fOffDenseInW = 0;
497 fOffDenseInB = fOffDenseInW + in * w;
499 fBlockSize = block_size;
500 fOffFinalLnG = fOffBlocks + n_blocks * block_size;
503 fOffDenseOutB = fOffDenseOutW + w * out;
504
505 fNTests = static_cast<int>(n_tests);
506 fTestTolerance = tolerance;
507 fTestInputs = std::move(test_in);
508 fTestOutputs = std::move(test_out);
509 return true;
510}
511//------------------------------------------------------------------------------
512
513void BmnGemStripMLPModel::ForwardFromFirstDense(float* h, float* output) const
514{
515 const int w = fWidth;
516 const float* p = fParams.data();
517 float t[kMaxWidth];
518 float u[kMaxWidth];
519
520 LeakyReLU(h, w, fLeakyReLUSlope);
521
522 // Residual blocks: h = h + Dense2(LeakyReLU(Dense1(LayerNorm(h))))
523 for (int iblock = 0; iblock < fNBlocks; ++iblock) {
524 const float* blk = p + fOffBlocks + static_cast<size_t>(iblock) * fBlockSize;
525 const float* ln_g = blk;
526 const float* ln_b = ln_g + w;
527 const float* W1 = ln_b + w;
528 const float* b1 = W1 + static_cast<size_t>(w) * w;
529 const float* W2 = b1 + w;
530 const float* b2 = W2 + static_cast<size_t>(w) * w;
531
532 LayerNorm(h, w, ln_g, ln_b, fLayerNormEps, t);
533 Dense(t, w, W1, b1, w, u);
534 LeakyReLU(u, w, fLeakyReLUSlope);
535 Dense(u, w, W2, b2, w, t);
536 for (int i = 0; i < w; ++i)
537 h[i] += t[i];
538 }
539
540 // Final LayerNorm and Dense
541 LayerNorm(h, w, p + fOffFinalLnG, p + fOffFinalLnB, fLayerNormEps, t);
542 Dense(t, w, p + fOffDenseOutW, p + fOffDenseOutB, fOutputDim, output);
543
545 for (int j = 0; j < fOutputDim; ++j)
546 output[j] = 1.0f / (1.0f + std::exp(-output[j]));
547 }
548}
549//------------------------------------------------------------------------------
550
551bool BmnGemStripMLPModel::Predict(const float* input, float* output) const
552{
553 if (!fLoaded || !input || !output)
554 return false;
555
556 const int w = fWidth;
557 float h[kMaxWidth];
558 int rows[kMaxDim];
559 float vals[kMaxDim];
560
561 // First Dense (zero inputs are skipped: the segment features are often 0)
562 std::memcpy(h, fParams.data() + fOffDenseInB, sizeof(float) * w);
563 const int nnz = CompactNonZero(input, fInputDim, 0, rows, vals);
564 AccumulateRows<true>(h, w, fParams.data() + fOffDenseInW, vals, rows, nnz);
565
566 ForwardFromFirstDense(h, output);
567 return true;
568}
569
570std::vector<float> BmnGemStripMLPModel::Predict(const std::vector<float>& input) const
571{
572 if (!fLoaded || static_cast<int>(input.size()) != fInputDim)
573 return {};
574 std::vector<float> output(fOutputDim);
575 if (!Predict(input.data(), output.data()))
576 return {};
577 return output;
578}
579//------------------------------------------------------------------------------
580
581bool BmnGemStripMLPModel::PredictSplit(const float* common,
582 int n_common,
583 const float* specific,
584 int n_rows,
585 float* output) const
586{
587 if (!fLoaded || !output || n_rows < 0 || n_common < 0 || n_common > fInputDim)
588 return false;
589 const int n_spec = fInputDim - n_common;
590 if ((n_common > 0 && !common) || (n_spec > 0 && n_rows > 0 && !specific))
591 return false;
592
593 const int w = fWidth;
594 const float* W = fParams.data() + fOffDenseInW;
595 int rows[kMaxDim];
596 float vals[kMaxDim];
597
598 // First Dense, common part (once for all rows)
599 float base[kMaxWidth];
600 std::memcpy(base, fParams.data() + fOffDenseInB, sizeof(float) * w);
601 const int nnz_common = CompactNonZero(common, n_common, 0, rows, vals);
602 AccumulateRows<true>(base, w, W, vals, rows, nnz_common);
603
604 // Specific part and the rest of the network for each row
605 float h[kMaxWidth];
606 for (int r = 0; r < n_rows; ++r) {
607 std::memcpy(h, base, sizeof(float) * w);
608 const int nnz = CompactNonZero(specific + static_cast<size_t>(r) * n_spec, n_spec, n_common, rows, vals);
609 AccumulateRows<true>(h, w, W, vals, rows, nnz);
610 ForwardFromFirstDense(h, output + static_cast<size_t>(r) * fOutputDim);
611 }
612 return true;
613}
614//------------------------------------------------------------------------------
615
616const float* BmnGemStripMLPModel::GetTestInput(int itest) const
617{
618 if (itest < 0 || itest >= fNTests)
619 return nullptr;
620 return fTestInputs.data() + static_cast<size_t>(itest) * fInputDim;
621}
622
623const float* BmnGemStripMLPModel::GetTestOutput(int itest) const
624{
625 if (itest < 0 || itest >= fNTests)
626 return nullptr;
627 return fTestOutputs.data() + static_cast<size_t>(itest) * fOutputDim;
628}
629
630bool BmnGemStripMLPModel::RunSelfTest(float* max_abs_diff, bool verbose) const
631{
632 if (max_abs_diff)
633 *max_abs_diff = 0.0f;
634 if (!fLoaded)
635 return false;
636
637 std::vector<float> out(fOutputDim);
638 float max_diff = 0.0f;
639 for (int itest = 0; itest < fNTests; ++itest) {
640 Predict(GetTestInput(itest), out.data());
641 const float* ref = GetTestOutput(itest);
642 for (int j = 0; j < fOutputDim; ++j) {
643 const float d = std::fabs(out[j] - ref[j]);
644 if (!(d <= max_diff)) // NaN is also caught
645 max_diff = std::isfinite(d) ? d : std::numeric_limits<float>::infinity();
646 }
647 }
648 if (max_abs_diff)
649 *max_abs_diff = max_diff;
650
651 const bool ok = max_diff <= fTestTolerance;
652 if (verbose)
653 LOG(info) << "BmnGemStripMLPModel: self-test on " << fNTests << " samples: max |output - Keras| = " << max_diff
654 << " (tolerance " << fTestTolerance << ") -> " << (ok ? "OK" : "FAILED");
655 return ok;
656}
657//------------------------------------------------------------------------------
658
659int BmnGemStripMLPModel::GetFeatureIndex(const std::string& name) const
660{
661 auto it = std::find(fFeatureNames.begin(), fFeatureNames.end(), name);
662 return (it == fFeatureNames.end()) ? -1 : static_cast<int>(it - fFeatureNames.begin());
663}
664
665bool BmnGemStripMLPModel::CheckFeatureNames(const std::vector<std::string>& expected, std::string* why) const
666{
667 if (fFeatureNames.size() != expected.size()) {
668 if (why)
669 *why = "model has " + std::to_string(fFeatureNames.size()) + " input features, expected "
670 + std::to_string(expected.size());
671 return false;
672 }
673 for (size_t i = 0; i < expected.size(); ++i) {
674 if (fFeatureNames[i] != expected[i]) {
675 if (why)
676 *why = "input feature " + std::to_string(i) + " is '" + fFeatureNames[i] + "', expected '" + expected[i]
677 + "'";
678 return false;
679 }
680 }
681 return true;
682}
683
684void BmnGemStripMLPModel::Print(std::ostream& os) const
685{
686 if (!fLoaded) {
687 os << "BmnGemStripMLPModel: not loaded\n";
688 return;
689 }
690 os << "BmnGemStripMLPModel: " << fFilePath << "\n"
691 << " residual MLP: input " << fInputDim << " -> width " << fWidth << " x " << fNBlocks << " blocks -> output "
692 << fOutputDim << (fOutputActivation == kSigmoidOutput ? " (sigmoid)" : " (linear)") << "\n"
693 << " parameters: " << fParams.size() << ", LayerNorm eps = " << fLayerNormEps
694 << ", LeakyReLU slope = " << fLeakyReLUSlope << "\n"
695 << " source: " << fSourceName << ", arch_hash: " << fArchHash << ", created: " << fCreated << "\n"
696 << " test samples: " << fNTests << " (tolerance " << fTestTolerance << ")\n"
697 << " features:";
698 for (size_t i = 0; i < fFeatureNames.size(); ++i)
699 os << (i % 16 == 0 ? "\n " : " ") << fFeatureNames[i];
700 os << "\n";
701}
702//------------------------------------------------------------------------------
703
704std::shared_ptr<const BmnGemStripMLPModel> BmnGemStripMLPModel::GetShared(const std::string& path)
705{
706 SharedState& state = Shared();
707 std::lock_guard<std::mutex> lock(state.mutex);
708
709 // Cache key - the expanded path (the same file given by different strings is loaded once);
710 // the expansion is done once for each path string (GetShared() is called for each point)
711 const std::string& given = path.empty() ? state.default_path : path;
712 auto iexp = state.expanded.find(given);
713 if (iexp == state.expanded.end())
714 iexp = state.expanded.emplace(given, ExpandPathName(given)).first;
715 const std::string key = iexp->second;
716
717 auto it = state.models.find(key);
718 if (it != state.models.end())
719 return it->second;
720 if (state.failed.count(key))
721 return nullptr;
722
723 auto model = std::make_shared<BmnGemStripMLPModel>();
724 if (!model->Load(key, true)) {
725 LOG(warn) << "BmnGemStripMLPModel: the model " << key
726 << " is not available (this warning is printed once for the path)";
727 state.failed.insert(key);
728 return nullptr;
729 }
730 std::shared_ptr<const BmnGemStripMLPModel> shared = model;
731 state.models[key] = shared;
732 return shared;
733}
734
735void BmnGemStripMLPModel::SetDefaultPath(const std::string& path)
736{
737 SharedState& state = Shared();
738 std::lock_guard<std::mutex> lock(state.mutex);
739 state.default_path = path.empty() ? std::string(kBuiltinDefaultPath) : path;
740}
741
743{
744 SharedState& state = Shared();
745 std::lock_guard<std::mutex> lock(state.mutex);
746 return state.default_path;
747}
748
750{
751 return kBuiltinDefaultPath;
752}
753
755{
756 SharedState& state = Shared();
757 std::lock_guard<std::mutex> lock(state.mutex);
758 state.models.clear(); // models in use stay alive while someone holds them
759 state.failed.clear();
760 state.expanded.clear();
761}
vector< Double_t > W(vector< Double_t > dist, Double_t sig)
Definition BmnMath.cxx:878
#define F32
__m128 v
Definition P4_F32vec4.h:1
int i
Definition P4_F32vec4.h:22
nsL1vector __attribute__
M-Stream Header https://afi.jinr.ru/MpdDeviceRawDataFormat.
R__EXTERN TSystem * gSystem
static std::string GetBuiltinDefaultPath()
const float * GetTestOutput(int itest) const
const float * GetTestInput(int itest) const
bool CheckFeatureNames(const std::vector< std::string > &expected, std::string *why=nullptr) const
bool PredictSplit(const float *common, int n_common, const float *specific, int n_rows, float *output) const
static std::string GetDefaultPath()
static constexpr int kMaxWidth
bool RunSelfTest(float *max_abs_diff=nullptr, bool verbose=false) const
static constexpr int kMaxDim
void ForwardFromFirstDense(float *h, float *output) const
bool Parse(const std::vector< unsigned char > &buffer, std::string &error)
bool Predict(const float *input, float *output) const
static void SetDefaultPath(const std::string &path)
int GetFeatureIndex(const std::string &name) const
std::vector< float > fTestInputs
std::vector< std::string > fFeatureNames
static std::shared_ptr< const BmnGemStripMLPModel > GetShared(const std::string &path="")
std::vector< float > fTestOutputs
bool Load(const std::string &path, bool verbose=true)
void Print(std::ostream &os=std::cout) const
std::vector< float > fParams
double beta(double x, double y)
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:20362
double gamma(double x)
*brief create JSON pointer *sa reference_tokens const std::string const std::string & b
Definition json.hpp:11787