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";
51uint32_t Crc32(
const unsigned char* data,
size_t n)
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) {
58 for (
int k = 0; k < 8; ++k)
59 c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
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;
72 const uint32_t one = 1;
74 std::memcpy(&first, &one, 1);
82 Reader(
const unsigned char* data,
size_t size)
88 bool Bytes(
void* dst,
size_t n)
92 std::memcpy(dst, fData + fPos,
n);
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)
100 if (
n > (fSize - fPos) /
sizeof(
float))
103 return n == 0 || Bytes(
v.data(),
n *
sizeof(
float));
105 bool Str(std::string& s)
108 if (!U32(len) || len > (1u << 20) || len > fSize - fPos)
110 s.assign(
reinterpret_cast<const char*
>(fData + fPos), len);
114 size_t Pos()
const {
return fPos; }
117 const unsigned char* fData;
130inline v4f Load4(
const float* p)
133 std::memcpy(&
v, p,
sizeof(
v));
137inline void Store4(
float* p, v4f
v)
139 std::memcpy(p, &
v,
sizeof(
v));
143template<
bool kIndexed>
144inline void AccumulateRows(
float* __restrict y,
146 const float* __restrict
W,
147 const float* __restrict xs,
148 const int* __restrict rows,
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};
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);
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);
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);
188 for (; j < n_out; ++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];
197inline int CompactNonZero(
const float* x,
int n,
int offset,
int* rows,
float* vals)
200 for (
int i = 0;
i <
n; ++
i) {
202 rows[nnz] = offset +
i;
211inline void Dense(
const float* __restrict x,
213 const float* __restrict
W,
214 const float* __restrict b,
218 std::memcpy(y, b,
sizeof(
float) * n_out);
219 AccumulateRows<false>(y, n_out,
W, x,
nullptr, n_in);
222inline void LeakyReLU(
float* x,
int n,
float slope)
224 for (
int i = 0;
i <
n; ++
i)
225 x[
i] = (x[
i] > 0.0f) ? x[
i] : slope * x[
i];
229inline void LayerNorm(
const float* __restrict x,
231 const float* __restrict gamma,
232 const float* __restrict
beta,
237 for (
int i = 0;
i <
n; ++
i)
239 const double mean = sum /
n;
241 for (
int i = 0;
i <
n; ++
i) {
242 const double d = x[
i] - mean;
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)
254std::string ExpandPathName(
const std::string& path,
bool* ok =
nullptr)
256 TString expanded(path.c_str());
257 const bool failed =
gSystem->ExpandPathName(expanded);
260 return failed ? path : std::string(expanded.Data());
267 std::string default_path = kBuiltinDefaultPath;
268 std::map<std::string, std::shared_ptr<const BmnGemStripMLPModel>> models;
269 std::set<std::string> failed;
270 std::map<std::string, std::string> expanded;
275 static SharedState state;
313 std::vector<float>().swap(
fParams);
328 bool expanded_ok =
true;
329 const std::string full_path = ExpandPathName(path, &expanded_ok);
331 auto fail = [&](
const std::string& reason) {
333 LOG(error) <<
"BmnGemStripMLPModel: cannot load model " << full_path <<
": " << reason;
338 if (!IsLittleEndian())
339 return fail(
"only little-endian platforms are supported");
341 std::ifstream file(full_path, std::ios::binary | std::ios::ate);
343 std::string reason =
"file not found or not readable";
345 reason +=
" (an environment variable in the path is not set)";
349 const std::streamsize size = file.tellg();
351 return fail(
"empty file");
352 std::vector<unsigned char> buffer(
static_cast<size_t>(size));
354 if (!file.read(
reinterpret_cast<char*
>(buffer.data()), size))
355 return fail(
"read error");
358 if (!
Parse(buffer, error))
365 float max_diff = 0.0f;
367 std::ostringstream reason;
368 reason <<
"self-test failed: max |output - Keras| = " << max_diff <<
" > tolerance " <<
fTestTolerance;
369 return fail(reason.str());
379 if (buffer.size() <
sizeof(kMagic) + 4 *
sizeof(uint32_t)) {
380 error =
"file is too short";
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)";
391 Reader rd(buffer.data(), data_size);
392 auto bad = [&](
const std::string& what) {
393 error =
"format error: " + what;
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)");
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) +
")";
410 return bad(
"no architecture type");
411 if (arch != kArchResidualMLP) {
412 error =
"unsupported architecture type " + std::to_string(arch);
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");
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");
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");
435 std::string arch_hash, source, created;
436 if (!rd.Str(arch_hash) || !rd.Str(source) || !rd.Str(created))
437 return bad(
"truncated strings");
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)
448 return bad(
"truncated feature names");
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;
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");
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");
479 if (rd.Pos() != data_size)
480 return bad(
"unexpected data at the end of the file");
484 fWidth =
static_cast<int>(width);
485 fNBlocks =
static_cast<int>(n_blocks);
505 fNTests =
static_cast<int>(n_tests);
516 const float* p =
fParams.data();
523 for (
int iblock = 0; iblock <
fNBlocks; ++iblock) {
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;
533 Dense(t, w, W1, b1, w, u);
535 Dense(u, w, W2, b2, w, t);
536 for (
int i = 0;
i < w; ++
i)
546 output[j] = 1.0f / (1.0f + std::exp(-output[j]));
553 if (!
fLoaded || !input || !output)
563 const int nnz = CompactNonZero(input,
fInputDim, 0, rows, vals);
575 if (!
Predict(input.data(), output.data()))
583 const float* specific,
587 if (!
fLoaded || !output || n_rows < 0 || n_common < 0 || n_common >
fInputDim)
590 if ((n_common > 0 && !common) || (n_spec > 0 && n_rows > 0 && !specific))
601 const int nnz_common = CompactNonZero(common, n_common, 0, rows, vals);
602 AccumulateRows<true>(base, w,
W, vals, rows, nnz_common);
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);
618 if (itest < 0 || itest >=
fNTests)
625 if (itest < 0 || itest >=
fNTests)
633 *max_abs_diff = 0.0f;
638 float max_diff = 0.0f;
639 for (
int itest = 0; itest <
fNTests; ++itest) {
643 const float d = std::fabs(out[j] - ref[j]);
644 if (!(d <= max_diff))
645 max_diff = std::isfinite(d) ? d : std::numeric_limits<float>::infinity();
649 *max_abs_diff = max_diff;
653 LOG(info) <<
"BmnGemStripMLPModel: self-test on " <<
fNTests <<
" samples: max |output - Keras| = " << max_diff
654 <<
" (tolerance " <<
fTestTolerance <<
") -> " << (ok ?
"OK" :
"FAILED");
669 *why =
"model has " + std::to_string(
fFeatureNames.size()) +
" input features, expected "
670 + std::to_string(expected.size());
673 for (
size_t i = 0;
i < expected.size(); ++
i) {
676 *why =
"input feature " + std::to_string(
i) +
" is '" +
fFeatureNames[
i] +
"', expected '" + expected[
i]
687 os <<
"BmnGemStripMLPModel: not loaded\n";
690 os <<
"BmnGemStripMLPModel: " <<
fFilePath <<
"\n"
706 SharedState& state = Shared();
707 std::lock_guard<std::mutex> lock(state.mutex);
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;
717 auto it = state.models.find(key);
718 if (it != state.models.end())
720 if (state.failed.count(key))
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);
730 std::shared_ptr<const BmnGemStripMLPModel> shared = model;
731 state.models[key] = shared;
737 SharedState& state = Shared();
738 std::lock_guard<std::mutex> lock(state.mutex);
739 state.default_path = path.empty() ? std::string(kBuiltinDefaultPath) : path;
744 SharedState& state = Shared();
745 std::lock_guard<std::mutex> lock(state.mutex);
746 return state.default_path;
751 return kBuiltinDefaultPath;
756 SharedState& state = Shared();
757 std::lock_guard<std::mutex> lock(state.mutex);
758 state.models.clear();
759 state.failed.clear();
760 state.expanded.clear();
vector< Double_t > W(vector< Double_t > dist, Double_t sig)
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 void ClearSharedCache()
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
virtual ~BmnGemStripMLPModel()
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
*brief create JSON pointer *sa reference_tokens const std::string const std::string & b