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;
473inline void Gemm_Call_pushforward(float *output, bool transa, bool transb, int m, int n, int k, float alpha,
474 const float *A, const float *B, float beta, const float *C, float *_d_output, bool,
475 bool, int, int, int, float _d_alpha, const float *_d_A, const float *_d_B,
476 float _d_beta, const float *_d_C)
478 // Primal: output = alpha * op(A) op(B) + beta * C, where a null C means
479 // "accumulate onto the existing output" (see Gemm_Call).
481 // d_output = alpha * (op(dA) op(B) + op(A) op(dB)) + beta * dC
482 // + d_alpha * op(A) op(B) + d_beta * C
483 // with (C, dC) read as (output, d_output) in accumulate mode. The tangent
484 // is therefore computed first, while output and d_output still hold the
485 // values the primal call overwrites. The pointer null-checks are hoisted
486 // into bools: with a pointer-typed ternary condition, Clad's reverse pass
487 // (as of v2.4) hoists and tapes the condition with the const qualifier
488 // dropped, generating code that does not compile.
489 const bool hasC = C != nullptr;
490 const bool hasdC = _d_C != nullptr;
491 SOFIE_MODEL_NS::Gemm_Call(_d_output, transa, transb, m, n, k, alpha, _d_A, B, (hasC && !hasdC) ? 0.0f : beta, _d_C);
492 SOFIE_MODEL_NS::Gemm_Call(_d_output, transa, transb, m, n, k, alpha, A, _d_B, 1.0f, nullptr);
493 if (_d_alpha != 0.0f) {
494 SOFIE_MODEL_NS::Gemm_Call(_d_output, transa, transb, m, n, k, _d_alpha, A, B, 1.0f, nullptr);
496 if (_d_beta != 0.0f) {
498 for (int i = 0; i < m * n; ++i) {
499 _d_output[i] += _d_beta * C[i];
502 for (int i = 0; i < m * n; ++i) {
503 _d_output[i] += _d_beta * output[i];
507 SOFIE_MODEL_NS::Gemm_Call(output, transa, transb, m, n, k, alpha, A, B, beta, C);
512inline void Copy_pushforward(float *output, const float *input, int size, float *_d_output, const float *_d_input,
515 for (int i = 0; i < size; i++) {
516 output[i] = input[i];
517 _d_output[i] = _d_input[i];
523inline void Fill_pushforward(float *output, float value, int size, float *_d_output, float _d_value, int)
525 for (int i = 0; i < size; i++) {
527 _d_output[i] = _d_value;
533inline void Relu_pushforward(float *output, float const *input, int size, float *_d_output, float const *_d_input,
536 // Tangent first: the generated code applies Relu in place, so input/output
537 // (and their tangents) may alias.
538 for (int i = 0; i < size; i++) {
539 _d_output[i] = (input[i] > 0.0f) ? _d_input[i] : 0.0f;
540 output[i] = (input[i] > 0.0f) ? input[i] : 0.0f;
545constexpr const char *
kRelu = R
"SOFIE(
546inline void Relu(float *output, float const *input, int size)
548 for (int i = 0; i < size; i++) {
549 output[i] = (input[i] > 0.0f) ? input[i] : 0.0f;
554constexpr const char *kFill = R
"SOFIE(
555inline void Fill(float *output, float value, int size)
557 std::fill(output, output + size, value);
561constexpr const char *kCopy = R
"SOFIE(
563inline void Copy(T *output, T const *input, int size)
565 std::copy(input, input + size, output);
570inline float ParseFloatToken(const std::string &s)
573 return std::numeric_limits<float>::infinity();
575 return -std::numeric_limits<float>::infinity();
577 return std::numeric_limits<float>::quiet_NaN();
582void ReadTensorFromStream(std::istream &is, T &target, std::string const &expectedName, std::size_t expectedLength)
586 is >> name >> length;
587 if (name != expectedName) {
588 std::string err_msg =
589 "TMVA-SOFIE failed to read the correct tensor name; expected name is " + expectedName + " , read " + name;
590 throw std::runtime_error(err_msg);
592 if (length != expectedLength) {
593 std::string err_msg = "TMVA-SOFIE failed to read the correct tensor size; expected size is " +
594 std::to_string(expectedLength) + " , read " + std::to_string(length);
595 throw std::runtime_error(err_msg);
598 for (std::size_t i = 0; i < length; ++i) {
600 target[i] = ParseFloatToken(token);
603 throw std::runtime_error("TMVA-SOFIE failed to read the values for tensor " + expectedName);
612 enum class Kind { Static, Symbolic };
615 std::string_view name;
616 constexpr SingleDim(std::size_t v) : kind(Kind::Static), dim(v), name() {}
617 constexpr SingleDim(const char *v) : kind(Kind::Symbolic), dim(0), name(v) {}
621 const SingleDim *data;
623 constexpr std::size_t total_size() const
625 std::size_t result = 1;
626 for (std::size_t i = 0; i < size; ++i) {
627 result *= data[i].dim;
634constexpr TensorDims makeDims(Arr const &arr)
636 return TensorDims{arr.data(), arr.size()};
641struct TensorLifeInfo {
642 int begin; // start time (operator index) of the tensor's lifetime
643 int end; // end time (operator index)
644 std::size_t size; // size in bytes
648 std::size_t total_bytes = 0; // total memory needed
649 std::vector<std::size_t> offsets; // resulting offset for each tensor
652namespace memory_detail {
656 // order by offset for deterministic coalescing
657 bool operator<(const FreeBlock &other) const { return offset < other.offset; }
660 int t; // time (operator index)
661 int type; // 0 = END, 1 = START
662 int idx; // tensor index
663 bool operator<(const MemoryEvent &o) const
667 return type < o.type; // END before START at the same time
670} // namespace memory_detail
672// Greedy best-fit planner with a coalescing free list.
673inline MemoryResult OrganizeMemory(const std::vector<TensorLifeInfo> &tensorsInfo)
675 using memory_detail::FreeBlock;
676 using memory_detail::MemoryEvent;
677 for (const auto &t : tensorsInfo) {
678 if (!(t.end > t.begin)) {
679 throw std::runtime_error("Each tensor must have end > begin.");
683 std::vector<MemoryEvent> events;
684 events.reserve(tensorsInfo.size() * 2);
685 for (int i = 0; i < (int)tensorsInfo.size(); ++i) {
686 events.push_back({tensorsInfo[i].end, 0, i});
687 events.push_back({tensorsInfo[i].begin, 1, i});
689 std::sort(events.begin(), events.end());
691 std::vector<std::size_t> tensorsOffset(tensorsInfo.size());
692 std::set<FreeBlock> free_list;
693 std::unordered_map<int, std::size_t> live_size;
694 std::unordered_map<int, std::size_t> live_offset;
695 std::size_t total_bytes = 0;
697 auto allocate_best_fit = [&](std::size_t need) -> std::size_t {
698 // Smallest free block with size >= need. free_list is ordered by offset, so
699 // this scans linearly; for very large tensor sets a size-keyed multimap
700 // would avoid the O(n) scan.
701 auto best = free_list.end();
702 for (auto it = free_list.begin(); it != free_list.end(); ++it) {
703 if (it->size >= need) {
704 if (best == free_list.end() || it->size < best->size)
708 if (best != free_list.end()) {
709 std::size_t off = best->offset;
710 if (best->size == need) {
711 free_list.erase(best);
713 FreeBlock updated{best->offset + need, best->size - need};
714 free_list.erase(best);
715 free_list.insert(updated);
719 std::size_t off = total_bytes;
724 auto try_coalesce = [&](std::set<FreeBlock>::iterator it) {
725 if (it != free_list.begin()) {
726 auto prev = std::prev(it);
727 if (prev->offset + prev->size == it->offset) {
728 FreeBlock merged{prev->offset, prev->size + it->size};
729 free_list.erase(prev);
730 it = free_list.erase(it);
731 it = free_list.insert(merged).first;
734 auto next = std::next(it);
735 if (next != free_list.end() && it->offset + it->size == next->offset) {
736 FreeBlock merged{it->offset, it->size + next->size};
737 free_list.erase(next);
738 it = free_list.erase(it);
739 free_list.insert(merged);
743 for (const auto &e : events) {
745 auto it_sz = live_size.find(e.idx);
746 auto it_off = live_offset.find(e.idx);
747 if (it_sz != live_size.end() && it_off != live_offset.end()) {
748 FreeBlock fb{it_off->second, it_sz->second};
749 auto it = free_list.insert(fb).first;
751 live_size.erase(it_sz);
752 live_offset.erase(it_off);
755 auto &t = tensorsInfo[e.idx];
756 std::size_t off = allocate_best_fit(t.size);
757 tensorsOffset[e.idx] = off;
758 live_size[e.idx] = t.size;
759 live_offset[e.idx] = off;
763 return MemoryResult{total_bytes, std::move(tensorsOffset)};
772constexpr const char *
kGNNData = R
"SOFIE(
773using GNN_Data = TMVA::Experimental::SOFIE::GNN_Data;
788 const bool gemm =
need(
"Gemm_Call");
790 const bool fill =
need(
"Fill");
791 const bool copy =
need(
"Copy");
804 auto addStd = [&](std::initializer_list<const char *>
hs) {
812 addStd({
"vector",
"cstddef"});
814 addStd({
"sstream",
"string",
"stdexcept"});
816 addStd({
"string",
"istream",
"stdexcept",
"limits"});
818 addStd({
"array",
"string_view",
"cstddef"});
820 addStd({
"set",
"unordered_map",
"stdexcept",
"iterator"});
826 std::string includes;
828 includes +=
"#include <" +
h +
">\n";
830 includes +=
"#include \"" +
h +
"\"\n";
835 defs +=
"\n// --- Standalone SOFIE inference helper functions ---\n";
847 defs +=
"\nnamespace UTILITY {\n";
860 defs +=
"} // namespace UTILITY\n";
880 defs +=
"// --- End of SOFIE inference helper functions ---\n\n";
919 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...