11namespace Experimental {
25extern "C" void sgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k,
26 const float *alpha, const float *A, const int *lda, const float *B, const int *ldb,
27 const float *beta, float *C, const int *ldc);
32inline std::size_t ConvertShapeToLength(const std::vector<std::size_t> &shape)
34 std::size_t length = 1;
35 for (auto &dim : shape)
42inline std::string ConvertShapeToString(const std::vector<std::size_t> &shape)
44 std::stringstream out;
46 for (std::size_t i = 0; i < shape.size(); i++) {
48 if (i < shape.size() - 1)
59inline bool is_a_ge_zero_and_a_lt_b(int a, int b)
61 return static_cast<unsigned>(a) < static_cast<unsigned>(b);
79constexpr const char *
kIm2col = R
"SOFIE(
81void Im2col(const T *data_im, const int channels, const int height, const int width, const int kernel_h,
82 const int kernel_w, const int pad_h_begin, const int pad_h_end, const int pad_w_begin,
83 const int pad_w_end, const int stride_h, const int stride_w,
84 const int dilation_h, const int dilation_w, T *data_col)
86 const int output_h = (height + pad_h_begin + pad_h_end - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
87 const int output_w = (width + pad_w_begin + pad_w_end - (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
88 const int channel_size = height * width;
89 for (int channel = channels; channel--; data_im += channel_size) {
90 for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {
91 for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {
92 int input_row = -pad_h_begin + kernel_row * dilation_h;
93 for (int output_rows = output_h; output_rows; output_rows--) {
94 if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
95 for (int output_cols = output_w; output_cols; output_cols--) {
99 int input_col = -pad_w_begin + kernel_col * dilation_w;
100 for (int output_col = output_w; output_col; output_col--) {
101 if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
102 *(data_col++) = data_im[input_row * width + input_col];
106 input_col += stride_w;
109 input_row += stride_h;
117constexpr const char *
kIm2col3d = R
"SOFIE(
119void Im2col_3d(const T *data_im, const int channels,
120 const int depth, const int height, const int width,
121 const int kernel_d, const int kernel_h, const int kernel_w,
122 const int pad_d_begin, const int pad_d_end, const int pad_h_begin, const int pad_h_end,
123 const int pad_w_begin, const int pad_w_end,
124 const int stride_d, const int stride_h, const int stride_w,
125 const int dilation_d, const int dilation_h, const int dilation_w, T *data_col)
127 const int output_h = (height + pad_h_begin + pad_h_end - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
128 const int output_w = (width + pad_w_begin + pad_w_end - (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
129 const int output_d = (depth + pad_d_begin + pad_d_end - (dilation_d * (kernel_d - 1) + 1)) / stride_d + 1;
130 const int channel_size = height * width * depth;
131 for (int channel = channels; channel--; data_im += channel_size) {
132 for (int kernel_depth = 0; kernel_depth < kernel_d; kernel_depth++) {
133 for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {
134 for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {
135 int input_dep = -pad_d_begin + kernel_depth * dilation_d;
136 for (int output_dep = output_d; output_dep; output_dep--) {
137 if (!is_a_ge_zero_and_a_lt_b(input_dep, depth)) {
138 for (int output_rows = output_h; output_rows; output_rows--) {
139 for (int output_cols = output_w; output_cols; output_cols--) {
144 int input_row = -pad_h_begin + kernel_row * dilation_h;
145 for (int output_rows = output_h; output_rows; output_rows--) {
146 if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
147 for (int output_cols = output_w; output_cols; output_cols--) {
151 int input_col = -pad_w_begin + kernel_col * dilation_w;
152 for (int output_col = output_w; output_col; output_col--) {
153 if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
154 *(data_col++) = data_im[input_dep * width * height + input_row * width + input_col];
158 input_col += stride_w;
161 input_row += stride_h;
164 input_dep += stride_d;
173constexpr const char *
kCol2im = R
"SOFIE(
174template <typename Dtype>
175void col2im(const Dtype *data_col, const int channels,
176 const int height, const int width, const int kernel_h, const int kernel_w,
177 const int pad_h, const int pad_w,
178 const int stride_h, const int stride_w,
179 const int dilation_h, const int dilation_w,
182 // output must start zeroed: col2im scatters with += so overlapping columns accumulate
183 std::fill(data_im, data_im + height * width * channels, 0.);
184 const int output_h = (height + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
185 const int output_w = (width + 2 * pad_w - (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
186 const int channel_size = height * width;
187 for (int channel = channels; channel--; data_im += channel_size) {
188 for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {
189 for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {
190 int input_row = -pad_h + kernel_row * dilation_h;
191 for (int output_rows = output_h; output_rows; output_rows--) {
192 if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
193 data_col += output_w;
195 int input_col = -pad_w + kernel_col * dilation_w;
196 for (int output_col = output_w; output_col; output_col--) {
197 if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
198 data_im[input_row * width + input_col] += *data_col;
201 input_col += stride_w;
204 input_row += stride_h;
221void BroadcastTensor(const T *data, std::size_t curLength, const std::vector<std::size_t> &shape,
222 const std::vector<std::size_t> &targetShape, T *broadcastedData)
224 std::size_t size = shape.size();
225 if (size > 1 && shape.front() == targetShape.front() && shape.back() == 1) {
226 std::size_t bsize = targetShape.back();
227 for (int k = int(size) - 2; k >= 0; k--) {
230 bsize *= targetShape[k];
232 for (std::size_t i = 0; i < curLength; i++) {
233 std::fill(broadcastedData + i * bsize, broadcastedData + (i + 1) * bsize, data[i]);
238 std::copy(data, data + curLength, broadcastedData);
239 std::size_t arrayNum = 1;
240 std::vector<T> newData(ConvertShapeToLength(targetShape));
242 for (std::size_t idx = 0; idx < size; idx++) {
243 std::size_t dim = shape[idx];
244 std::size_t targetDim = targetShape[idx];
245 if (dim == 1 && targetDim > 1) {
246 std::size_t newLength = curLength * targetDim;
247 std::size_t arrayLength = curLength / arrayNum;
248 if (arrayLength > 1) {
249 for (std::size_t arrayIdx = 0; arrayIdx < arrayNum; arrayIdx++) {
250 for (std::size_t targetIdx = 0; targetIdx < targetDim; targetIdx++) {
251 std::size_t offset = arrayIdx * arrayLength * targetDim + targetIdx * arrayLength;
252 std::copy(broadcastedData + arrayIdx * arrayLength,
253 broadcastedData + (arrayIdx + 1) * arrayLength,
254 newData.begin() + offset);
258 for (std::size_t arrayIdx = 0; arrayIdx < arrayNum; arrayIdx++) {
259 std::fill(newData.begin() + arrayIdx * targetDim,
260 newData.begin() + (arrayIdx + 1) * targetDim, broadcastedData[arrayIdx]);
263 curLength = newLength;
264 std::copy(newData.begin(), newData.begin() + newLength, broadcastedData);
266 arrayNum *= targetDim;
271T *CreateBroadcastTensor(const T *data, const std::vector<std::size_t> &shape,
272 const std::vector<std::size_t> &targetShape, std::size_t targetLength)
274 T *broadcastedData = new T[targetLength];
275 std::size_t curLength = ConvertShapeToLength(shape);
276 BroadcastTensor<T>(data, curLength, shape, targetShape, broadcastedData);
277 return broadcastedData;
281T *UnidirectionalBroadcast(const T *data, const std::vector<std::size_t> &shape,
282 const std::vector<std::size_t> &targetShape)
284 if (shape.size() < targetShape.size()) {
285 std::size_t targetSize = targetShape.size();
286 std::vector<std::size_t> newShape(targetSize, 1);
287 std::size_t offset = targetSize - shape.size();
288 std::copy(shape.begin(), shape.end(), newShape.begin() + offset);
289 return CreateBroadcastTensor(data, newShape, targetShape, ConvertShapeToLength(targetShape));
291 return CreateBroadcastTensor(data, shape, targetShape, ConvertShapeToLength(targetShape));
295void UnidirectionalBroadcast(const T *data, const std::vector<std::size_t> &shape,
296 const std::vector<std::size_t> &targetShape, T *broadcastedData)
298 std::size_t curLength = ConvertShapeToLength(shape);
299 if (shape.size() < targetShape.size()) {
300 std::size_t targetSize = targetShape.size();
301 std::vector<std::size_t> newShape(targetSize, 1);
302 std::size_t offset = targetSize - shape.size();
303 std::copy(shape.begin(), shape.end(), newShape.begin() + offset);
304 BroadcastTensor(data, curLength, newShape, targetShape, broadcastedData);
307 BroadcastTensor(data, curLength, shape, targetShape, broadcastedData);
313T *BroadcastConvBias(const T *data, const std::size_t channel, const std::vector<std::size_t> &targetShape)
315 std::size_t size = targetShape.size();
316 if (targetShape[1] != channel) {
317 std::stringstream ss;
318 ss << "TMVA::SOFIE - Error broadcasting Conv Bias of shape {";
319 ss << std::to_string(channel);
321 ss << ConvertShapeToString(targetShape);
322 throw std::runtime_error(ss.str());
325 std::size_t targetLength = ConvertShapeToLength(targetShape);
326 T *newData = new T[targetLength];
328 if (targetLength == channel) {
329 std::copy(data, data + channel, newData);
333 std::size_t cStride = 1;
334 for (std::size_t i = 2; i < size; i++)
335 cStride *= targetShape[i];
336 for (std::size_t i = 0; i < channel; i++) {
337 std::fill(newData + i * cStride, newData + (i + 1) * cStride, data[i]);
339 std::size_t batch = targetShape[0];
340 std::size_t bStride = channel * cStride;
341 for (std::size_t i = 1; i < batch; i++) {
342 std::copy(newData, newData + bStride, newData + i * bStride);
348constexpr const char *
kGemmCall = R
"SOFIE(
349inline void Gemm_Call(float *output, bool transa, bool transb, int m, int n, int k, float alpha, const float *A,
350 const float *B, float beta, const float *C)
354 const int *lda = transa ? &k : &m;
355 const int *ldb = transb ? &n : &k;
358 std::copy(C, C + m * n, output);
360 BLAS::sgemm_(transa ? &ct : &cn, transb ? &ct : &cn, &m, &n, &k, &alpha, A, lda, B, ldb, &beta, output, ldc);
372inline void Gemm_Call_pullback(float *output, bool transa, bool transb, int m, int n, int k, float alpha,
373 const float *A, const float *B, float beta, const float *C, float *_d_output, bool *,
374 bool *, int *, int *, int *, float *_d_alpha, float *_d_A, float *_d_B, float *_d_beta,
378 // - fix and test the implementation for alpha != 1.0
383 // beta needs to be one because we want to add to _d_A and _d_B instead of
389 // dA += dY * op(B)^T
390 Gemm_Call(_d_A, false, !transb, m, k, n, one, _d_output, B, one, _d_A);
392 // dA += op(B) * dY^T
393 Gemm_Call(_d_A, transb, true, k, m, n, one, B, _d_output, one, _d_A);
398 // dB += op(A)^T * dY
399 Gemm_Call(_d_B, !transa, false, k, n, m, one, A, _d_output, one, _d_B);
401 // dB += dY^T * op(A)
402 Gemm_Call(_d_B, true, transa, n, k, m, one, _d_output, A, one, _d_B);
407 for (int i = 0; i < sizeC; ++i) {
409 *_d_alpha += _d_output[i] * (output[i] - beta * C[i]);
410 *_d_beta += _d_output[i] * C[i];
412 *_d_alpha += _d_output[i] * output[i];
415 _d_C[i] += _d_output[i] * beta;
425inline void Copy_pullback(float *output, const float *input, int size, float *_d_output, float *_d_input, int *)
427 for (int i = 0; i < size; i++) {
428 output[i] = input[i];
429 _d_input[i] += _d_output[i];
437inline void Fill_pullback(float *output, float value, int size, float *_d_output, float *_d_value, int *)
439 for (int i = 0; i < size; i++) {
441 *_d_value += _d_output[i];
451inline void Relu_pullback(float *output, const float *input, int size, float *_d_output, float *_d_input, int *)
453 for (int i = 0; i < size; i++) {
454 output[i] = input[i] > 0.F ? input[i] : 0.F;
455 float _r_d0 = _d_output[i];
458 _d_input[i] += _r_d0;
463constexpr const char *
kRelu = R
"SOFIE(
464inline void Relu(float *output, float const *input, int size)
466 for (int i = 0; i < size; i++) {
467 output[i] = (input[i] > 0.0f) ? input[i] : 0.0f;
472constexpr const char *kFill = R
"SOFIE(
473inline void Fill(float *output, float value, int size)
475 std::fill(output, output + size, value);
479constexpr const char *kCopy = R
"SOFIE(
481inline void Copy(T *output, T const *input, int size)
483 std::copy(input, input + size, output);
488inline float ParseFloatToken(const std::string &s)
491 return std::numeric_limits<float>::infinity();
493 return -std::numeric_limits<float>::infinity();
495 return std::numeric_limits<float>::quiet_NaN();
500void ReadTensorFromStream(std::istream &is, T &target, std::string const &expectedName, std::size_t expectedLength)
504 is >> name >> length;
505 if (name != expectedName) {
506 std::string err_msg =
507 "TMVA-SOFIE failed to read the correct tensor name; expected name is " + expectedName + " , read " + name;
508 throw std::runtime_error(err_msg);
510 if (length != expectedLength) {
511 std::string err_msg = "TMVA-SOFIE failed to read the correct tensor size; expected size is " +
512 std::to_string(expectedLength) + " , read " + std::to_string(length);
513 throw std::runtime_error(err_msg);
516 for (std::size_t i = 0; i < length; ++i) {
518 target[i] = ParseFloatToken(token);
521 throw std::runtime_error("TMVA-SOFIE failed to read the values for tensor " + expectedName);
530 enum class Kind { Static, Symbolic };
533 std::string_view name;
534 constexpr SingleDim(std::size_t v) : kind(Kind::Static), dim(v), name() {}
535 constexpr SingleDim(const char *v) : kind(Kind::Symbolic), dim(0), name(v) {}
539 const SingleDim *data;
541 constexpr std::size_t total_size() const
543 std::size_t result = 1;
544 for (std::size_t i = 0; i < size; ++i) {
545 result *= data[i].dim;
552constexpr TensorDims makeDims(Arr const &arr)
554 return TensorDims{arr.data(), arr.size()};
559struct TensorLifeInfo {
560 int begin; // start time (operator index) of the tensor's lifetime
561 int end; // end time (operator index)
562 std::size_t size; // size in bytes
566 std::size_t total_bytes = 0; // total memory needed
567 std::vector<std::size_t> offsets; // resulting offset for each tensor
570namespace memory_detail {
574 // order by offset for deterministic coalescing
575 bool operator<(const FreeBlock &other) const { return offset < other.offset; }
578 int t; // time (operator index)
579 int type; // 0 = END, 1 = START
580 int idx; // tensor index
581 bool operator<(const MemoryEvent &o) const
585 return type < o.type; // END before START at the same time
588} // namespace memory_detail
590// Greedy best-fit planner with a coalescing free list.
591inline MemoryResult OrganizeMemory(const std::vector<TensorLifeInfo> &tensorsInfo)
593 using memory_detail::FreeBlock;
594 using memory_detail::MemoryEvent;
595 for (const auto &t : tensorsInfo) {
596 if (!(t.end > t.begin)) {
597 throw std::runtime_error("Each tensor must have end > begin.");
601 std::vector<MemoryEvent> events;
602 events.reserve(tensorsInfo.size() * 2);
603 for (int i = 0; i < (int)tensorsInfo.size(); ++i) {
604 events.push_back({tensorsInfo[i].end, 0, i});
605 events.push_back({tensorsInfo[i].begin, 1, i});
607 std::sort(events.begin(), events.end());
609 std::vector<std::size_t> tensorsOffset(tensorsInfo.size());
610 std::set<FreeBlock> free_list;
611 std::unordered_map<int, std::size_t> live_size;
612 std::unordered_map<int, std::size_t> live_offset;
613 std::size_t total_bytes = 0;
615 auto allocate_best_fit = [&](std::size_t need) -> std::size_t {
616 // Smallest free block with size >= need. free_list is ordered by offset, so
617 // this scans linearly; for very large tensor sets a size-keyed multimap
618 // would avoid the O(n) scan.
619 auto best = free_list.end();
620 for (auto it = free_list.begin(); it != free_list.end(); ++it) {
621 if (it->size >= need) {
622 if (best == free_list.end() || it->size < best->size)
626 if (best != free_list.end()) {
627 std::size_t off = best->offset;
628 if (best->size == need) {
629 free_list.erase(best);
631 FreeBlock updated{best->offset + need, best->size - need};
632 free_list.erase(best);
633 free_list.insert(updated);
637 std::size_t off = total_bytes;
642 auto try_coalesce = [&](std::set<FreeBlock>::iterator it) {
643 if (it != free_list.begin()) {
644 auto prev = std::prev(it);
645 if (prev->offset + prev->size == it->offset) {
646 FreeBlock merged{prev->offset, prev->size + it->size};
647 free_list.erase(prev);
648 it = free_list.erase(it);
649 it = free_list.insert(merged).first;
652 auto next = std::next(it);
653 if (next != free_list.end() && it->offset + it->size == next->offset) {
654 FreeBlock merged{it->offset, it->size + next->size};
655 free_list.erase(next);
656 it = free_list.erase(it);
657 free_list.insert(merged);
661 for (const auto &e : events) {
663 auto it_sz = live_size.find(e.idx);
664 auto it_off = live_offset.find(e.idx);
665 if (it_sz != live_size.end() && it_off != live_offset.end()) {
666 FreeBlock fb{it_off->second, it_sz->second};
667 auto it = free_list.insert(fb).first;
669 live_size.erase(it_sz);
670 live_offset.erase(it_off);
673 auto &t = tensorsInfo[e.idx];
674 std::size_t off = allocate_best_fit(t.size);
675 tensorsOffset[e.idx] = off;
676 live_size[e.idx] = t.size;
677 live_offset[e.idx] = off;
681 return MemoryResult{total_bytes, std::move(tensorsOffset)};
690constexpr const char *
kGNNData = R
"SOFIE(
691using GNN_Data = TMVA::Experimental::SOFIE::GNN_Data;
706 const bool gemm =
need(
"Gemm_Call");
708 const bool fill =
need(
"Fill");
709 const bool copy =
need(
"Copy");
722 auto addStd = [&](std::initializer_list<const char *>
hs) {
730 addStd({
"vector",
"cstddef"});
732 addStd({
"sstream",
"string",
"stdexcept"});
734 addStd({
"string",
"istream",
"stdexcept",
"limits"});
736 addStd({
"array",
"string_view",
"cstddef"});
738 addStd({
"set",
"unordered_map",
"stdexcept",
"iterator"});
744 std::string includes;
746 includes +=
"#include <" +
h +
">\n";
748 includes +=
"#include \"" +
h +
"\"\n";
753 defs +=
"\n// --- Standalone SOFIE inference helper functions ---\n";
765 defs +=
"\nnamespace UTILITY {\n";
778 defs +=
"} // namespace UTILITY\n";
798 defs +=
"// --- End of SOFIE inference helper functions ---\n\n";
823 cladDefs +=
"} // namespace " +
modelNamespace +
"\n} // namespace custom_derivatives\n} // namespace clad\n";
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
HelperFunctionsCode GenerateHelperFunctionsCode(const std::set< std::string > &neededHelpers, const std::string &modelNamespace, bool sgemmAlreadyDeclared=false)
Return the standalone C++ source of the inference helper functions requested in neededHelpers (see RM...
create variable transformations
Source code of the inference helper functions to embed in generated code so that it is standalone and...