Skip to content

Commit 45f3e7b

Browse files
committed
[tmva][sofie] Add Concat operator
Use initial code from @sanjibansg and add implementation for axis = N-1. Still need to implement other axis cases. Add also function to compute tensor strides from tensor shape used in concat. Fix also some error messages in some other operators
1 parent 71ea6b3 commit 45f3e7b

File tree

5 files changed

+188
-31
lines changed

5 files changed

+188
-31
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#ifndef TMVA_SOFIE_ROPERATOR_Concat
2+
#define TMVA_SOFIE_ROPERATOR_Concat
3+
4+
5+
#include "TMVA/SOFIE_common.hxx"
6+
#include "TMVA/ROperator.hxx"
7+
#include "TMVA/RModel.hxx"
8+
9+
#include <sstream>
10+
#include <algorithm>
11+
#include <iterator>
12+
#include <iomanip>
13+
#include <limits>
14+
15+
namespace TMVA{
16+
namespace Experimental{
17+
namespace SOFIE{
18+
19+
template <typename T>
20+
class ROperator_Concat final : public ROperator
21+
{
22+
private:
23+
int fAxis;
24+
std::vector<std::string> fInputs;
25+
std::string fOutput;
26+
std::vector<size_t>fOutputShape;
27+
std::vector<std::vector<size_t>> fInputShapes;
28+
29+
public:
30+
ROperator_Concat(){}
31+
ROperator_Concat(std::vector<std::string> inputs, int axis, std::string output):
32+
fAxis(axis), fOutput(UTILITY::Clean_name(output)) {
33+
fInputs.reserve(inputs.size());
34+
for (auto & name : inputs)
35+
fInputs.push_back(UTILITY::Clean_name(name));
36+
}
37+
38+
std::vector<ETensorType> TypeInference(std::vector<ETensorType> input){
39+
return input;
40+
}
41+
42+
// get shape of output given inputs. It is going to be called after initialized
43+
std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> inputs){
44+
std::vector<std::vector<size_t>> ret(1);
45+
// treat negative axis case
46+
if (fAxis<0) {
47+
fAxis = inputs[0].size()+fAxis;
48+
}
49+
if (fAxis < 0 || fAxis >= (int) inputs[0].size())
50+
throw std::runtime_error("TMVA SOFIE Concat Op - invalid axis value ");
51+
52+
int concat_dim=0;
53+
for(size_t i = 0; i < inputs.size(); i++) {
54+
if (i > 0 && inputs[i].size() != inputs[i-1].size() )
55+
throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have different shapes " +
56+
ConvertShapeToString(inputs[i]) + " and " + ConvertShapeToString(inputs[i-1]));
57+
for (size_t iaxis = 0; iaxis < inputs[i].size(); iaxis++) {
58+
if ((int) iaxis == fAxis)
59+
concat_dim += inputs[i][iaxis];
60+
else
61+
if (i> 0 && inputs[i][iaxis] != inputs[i-1][iaxis])
62+
throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have wrong shapes " +
63+
ConvertShapeToString(inputs[i]) + " and " + ConvertShapeToString(inputs[i-1]));
64+
}
65+
}
66+
// output shape
67+
ret[0] = inputs[0];
68+
ret[0][fAxis] = concat_dim;
69+
return ret;
70+
}
71+
72+
void Initialize(RModel &model)
73+
{
74+
for (auto &it : fInputs) {
75+
if (model.CheckIfTensorAlreadyExist(it) == false) {
76+
throw std::runtime_error("TMVA SOFIE Concat Op Input Tensor " + it + " is not found in model");
77+
}
78+
fInputShapes.push_back(model.GetTensorShape(it));
79+
}
80+
fOutputShape = ShapeInference(fInputShapes)[0];
81+
model.AddIntermediateTensor(fOutput, model.GetTensorType(fInputs[0]), fOutputShape);
82+
}
83+
84+
std::string Generate(std::string OpName){
85+
OpName = "op_"+OpName;
86+
const std::string SP = " ";
87+
if(fOutputShape.empty()){
88+
throw std::runtime_error("TMVA SOFIE Concat called to Generate without being initialized first");
89+
}
90+
std::stringstream out;
91+
out<<"\n//--------- Concat\n";
92+
// special case for 0 axis that memory is contigous
93+
if (fAxis == 0) {
94+
for(auto &it : fInputs){
95+
out<<SP<<"tensor_" << fOutput<<".insert("<<"tensor_" << fOutput<<".end(),tensor_" << it<<".begin(),tensor_"<<it<<".end());\n";
96+
}
97+
}
98+
else {
99+
100+
std::vector<size_t> outStride = UTILITY::ComputeStrideFromShape(fOutputShape);
101+
std::vector<std::vector<size_t>> inStrides(fInputs.size());
102+
int i = 0;
103+
for ( auto &s : inStrides) {
104+
s = UTILITY::ComputeStrideFromShape(fInputShapes[i]);
105+
i++;
106+
}
107+
for (int i = 0; i < fAxis; ++i) {
108+
// loop on dimensions
109+
out << SP << "for (size_t i" << i << " = 0; i" << i << " < " << fOutputShape[i] << "; ++i" << i <<") {\n";
110+
}
111+
112+
out << SP << SP << SP << "int idxOut =";
113+
for (int k = 0; k < fAxis; k++)
114+
out << " + " << outStride[k] << "*i" << k;
115+
out << ";\n";
116+
117+
for (size_t j = 0; j < fInputs.size(); j++) {
118+
if (j>0)
119+
out << SP << SP << SP << "idxOut += " << fInputShapes[j-1][fAxis] << ";\n";
120+
out << SP << SP << SP << "int idxIn" << j <<" =";
121+
for (int k = 0; k < fAxis; k++)
122+
out << " + " << inStrides[j][k] << "*i" << k;
123+
out << ";\n";
124+
out << SP << SP << SP << "for (size_t iC = 0; iC < " << fInputShapes[j][fAxis] << "; ++iC) {\n";
125+
out << SP << SP << SP << SP << "tensor_" << fOutput << "[idxOut+iC] = tensor_" << fInputs[j] << "[idxIn" << j << "+iC];\n";
126+
out << SP << SP << SP << "}\n";
127+
// concatenate the axis values
128+
}
129+
for (int i = 0; i < fAxis; ++i) {
130+
out << SP << "}\n";
131+
}
132+
}
133+
134+
135+
return out.str();
136+
}
137+
138+
139+
};
140+
}//SOFIE
141+
}//Experimental
142+
}//TMVA
143+
144+
#endif //TMVA_SOFIE_ROPERATOR_CONCAT

tmva/sofie/inc/TMVA/ROperator_Relu.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public:
3737

3838
void Initialize(RModel& model){
3939
if (model.CheckIfTensorAlreadyExist(fNX) == false){ //input must be a graph input, or already initialized intermediate tensor
40-
throw std::runtime_error("TMVA SOFIE Relu Op Input Tensor is not found in model");
40+
throw std::runtime_error("TMVA SOFIE Relu Op Input Tensor " + fNX + " is not found in model");
4141
}
4242
fShape = model.GetTensorShape(fNX);
4343
model.AddIntermediateTensor(fNY, model.GetTensorType(fNX), fShape);

tmva/sofie/inc/TMVA/ROperator_Reshape.hxx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private:
2121

2222
ReshapeOpMode fOpMode = Reshape; // type of Reshape operator
2323

24-
int fAllowZero = 0; // (for Reshape) zero in tensor shape makes output shape equal to input tensor shape
24+
int fAllowZero = 0; // (for Reshape) zero in tensor shape makes output shape equal to input tensor shape
2525
int fAxis = 1; // (for Flatten)
2626

2727
std::string fNData; // input data tensor name
@@ -41,7 +41,7 @@ public:
4141
if (opMode == Reshape) fAllowZero = attr_value;
4242
if (opMode == Flatten) fAxis = attr_value;
4343
}
44-
44+
4545
// for squeeze/unsqueezed operators following old ONNX version (< 10)
4646
// IN this cases axes are passed as attribute values
4747
ROperator_Reshape(ReshapeOpMode opMode, std::vector<int64_t> attrAxes, std::string nameData, std::string nameOutput)
@@ -51,7 +51,7 @@ public:
5151
assert(fOpMode == Squeeze || fOpMode == Unsqueeze);
5252
}
5353

54-
// output type is same as input
54+
// output type is same as input
5555
std::vector<ETensorType> TypeInference(std::vector<ETensorType> input){
5656
auto ret = std::vector<ETensorType>(1, input[0]);
5757
return ret;
@@ -60,7 +60,7 @@ public:
6060
// output shape
6161
std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input){
6262
std::vector<std::vector<size_t>> ret;
63-
auto & input_shape = input[0];
63+
auto & input_shape = input[0];
6464

6565
if (fOpMode == Reshape) {
6666
if (input.size() != 2) throw std::runtime_error("TMVA SOFIE Reshape Op needs 2 input tensors");
@@ -96,7 +96,7 @@ public:
9696
ret.push_back(newShape);
9797

9898
} else if (fOpMode == Squeeze) {
99-
// squeeze
99+
// squeeze
100100
// assume no axis is provided - remove all axes with value equal to 1
101101
auto output_shape = input[0];
102102
if (input.size() == 1) {
@@ -105,7 +105,7 @@ public:
105105
output_shape.erase(output_shape.begin() + i);
106106
}
107107
}
108-
} else if (input.size() == 2) {
108+
} else if (input.size() == 2) {
109109
auto & axes = input[1];
110110
for (size_t i = 0; i < axes.size(); i++){
111111
if (output_shape[axes[i]] != 1)
@@ -125,7 +125,7 @@ public:
125125
if (axes[0] > 0) { // positive axis start from beginning
126126
for (auto & i : axes)
127127
output_shape.insert(output_shape.begin() + i, 1);
128-
} else {
128+
} else {
129129
//negative axes
130130
for (auto &i : axes) {
131131
assert(i < 0);
@@ -140,9 +140,9 @@ public:
140140
void Initialize(RModel &model)
141141
{
142142

143-
if (model.CheckIfTensorAlreadyExist(fNData) == false) {
143+
if (model.CheckIfTensorAlreadyExist(fNData) == false) {
144144
// input must be a graph input, or already initialized intermediate tensor
145-
throw std::runtime_error("TMVA Reshape Op Input Tensor is not found in model");
145+
throw std::runtime_error("TMVA Reshape Op Input Tensor " + fNData + " is not found in model");
146146
}
147147
fShapeInput = model.GetTensorShape(fNData);
148148

@@ -159,7 +159,7 @@ public:
159159
std::copy(input_shape, input_shape + n, descShape.begin());
160160
fShapeOutput = ShapeInference({fShapeInput, descShape})[0];
161161
} else {
162-
throw std::runtime_error("TMVA Reshape Op Input Tensor is not found in model");
162+
throw std::runtime_error("TMVA Reshape Op Shape Tensor " + fNShape + " is not found in model");
163163
}
164164
} else if (!fAttrAxes.empty()) {
165165
// case fNShape is empty and axes are provided as attributes

tmva/sofie/inc/TMVA/SOFIE_common.hxx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ template<typename T>
107107
T* Unidirectional_broadcast(const T* original_data, const std::vector<size_t> original_shape, const std::vector<size_t> target_shape);
108108
std::string Clean_name(std::string input_tensor_name);
109109

110+
/// compute stride of a tensor given its shape (assume layout is row-major)
111+
std::vector<size_t> ComputeStrideFromShape(const std::vector<size_t> & shape);
110112

111113
/// function to check if a >> 0 and a < MAX using a single comparison
112114
//// use trick casting to unsigned values so it becomes a single comparison
@@ -115,23 +117,23 @@ inline bool is_a_ge_zero_and_a_lt_b(int a, int b) {
115117
}
116118

117119

118-
/// im2col : efficient function to re-arrange input data of convolution to a matrix
120+
/// im2col : efficient function to re-arrange input data of convolution to a matrix
119121
/// that can be used by BLAS
120122
/// Use trick to loop on each element of filtered region first and follow input data layout
121123
/// By doing this reads and writes are of consecutive data in memory and one gains in efficiency
122-
/// The resulting matrix will be already transposed and can be used directly in BLAS
124+
/// The resulting matrix will be already transposed and can be used directly in BLAS
123125
/// since output will be a matrix : (channels*kernel_h*kernel_w , output_h*output_w)
124-
/// Example: with an input matrix
125-
/// a1 a2 a3
126-
/// b1 b2 b3 and a 2x2 kernel (k1,k2,k3,k4) and padding 1 :
127-
/// c1 c2 c3
128-
/// outpout will be a matrix (4 x 16)
129-
/// the routine will follow output order :
126+
/// Example: with an input matrix
127+
/// a1 a2 a3
128+
/// b1 b2 b3 and a 2x2 kernel (k1,k2,k3,k4) and padding 1 :
129+
/// c1 c2 c3
130+
/// outpout will be a matrix (4 x 16)
131+
/// the routine will follow output order :
130132
// first all elements which will be operated by k1 then k2 then k3
131133
/// -> ( 0 0 0 0 0 a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 ) all elements for k1
132-
/// ( 0 0 0 0 a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 0 ) for k2
133-
/// ( 0 a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 0 0 0 0 ) for k3
134-
/// ( a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 0 0 0 0 0 ) for k4
134+
/// ( 0 0 0 0 a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 0 ) for k2
135+
/// ( 0 a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 0 0 0 0 ) for k3
136+
/// ( a1 a2 a3 0 b1 b2 b3 0 c1 c2 c3 0 0 0 0 0 ) for k4
135137
///
136138

137139
template <typename T>
@@ -171,11 +173,11 @@ void Im2col(const T *data_im, const int channels, const int height, const int wi
171173

172174
/// 3d implementation
173175
template <typename T>
174-
void Im2col_3d(const T *data_im, const int channels,
175-
const int depth, const int height, const int width,
176-
const int kernel_d, const int kernel_h, const int kernel_w,
177-
const int pad_d, const int pad_h, const int pad_w,
178-
const int stride_d, const int stride_h, const int stride_w,
176+
void Im2col_3d(const T *data_im, const int channels,
177+
const int depth, const int height, const int width,
178+
const int kernel_d, const int kernel_h, const int kernel_w,
179+
const int pad_d, const int pad_h, const int pad_w,
180+
const int stride_d, const int stride_h, const int stride_w,
179181
const int dilation_d, const int dilation_h, const int dilation_w, T *data_col)
180182
{
181183
const int output_h = (height + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
@@ -201,7 +203,7 @@ void Im2col_3d(const T *data_im, const int channels,
201203
if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
202204
for (int output_cols = output_w; output_cols; output_cols--) {
203205
*(data_col++) = 0;
204-
}
206+
}
205207
} else {
206208
int input_col = -pad_w + kernel_col * dilation_w;
207209
for (int output_col = output_w; output_col; output_col--) {

tmva/sofie/src/SOFIE_common.cxx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ std::string ConvertShapeToString(std::vector<size_t> shape) {
4444
std::stringstream out;
4545
out << "{ ";
4646
for (size_t i = 0; i < shape.size(); i++) {
47-
out << shape[i];
47+
out << shape[i];
4848
if (i < shape.size()-1) out << " , ";
4949
}
5050
out << " }";
@@ -86,7 +86,7 @@ T* UTILITY::Unidirectional_broadcast(const T* original_data, const std::vector<s
8686
throw std::runtime_error(
8787
"TMVA::SOFIE Error in Broadcasting Tensor : original array has more dimensions than target shape," + originalShape + ", " + targetShape);
8888
}
89-
// if shape's sizes are different prepend 1 to get tensor with same shape size
89+
// if shape's sizes are different prepend 1 to get tensor with same shape size
9090
// since the broadcast is unidirectional we can only prepend
9191
std::vector<size_t> current_shape(original_shape);
9292
auto it = current_shape.begin();
@@ -95,7 +95,7 @@ T* UTILITY::Unidirectional_broadcast(const T* original_data, const std::vector<s
9595
}
9696
// this code below will work
9797
// when shape are not equal e.g. (3,4,5,6) and (3) and we add 1 in all missing positions
98-
// since broadcasting is uni-directional we do not use it
98+
// since broadcasting is uni-directional we do not use it
9999
// std::vector<size_t> current_shape(target_shape.size(),1);
100100
// for (size_t i = 0; i < original_shape.size(); i++) {
101101
// for (size_t j = 0; j < target_shape.size(); j++) {
@@ -143,6 +143,17 @@ std::string UTILITY::Clean_name(std::string input_tensor_name){
143143

144144
template float* UTILITY::Unidirectional_broadcast(const float* original_data, const std::vector<size_t> original_shape, const std::vector<size_t> target_shape);
145145

146+
std::vector<size_t> UTILITY::ComputeStrideFromShape(const std::vector<size_t> & shape) {
147+
// assume row major layout
148+
const auto size = shape.size();
149+
std::vector<size_t> strides(size,1);
150+
for (std::size_t i = 1; i < size; i++) {
151+
strides[size - 1 - i] = strides[size - 1 - i + 1] * shape[size - 1 - i + 1];
152+
}
153+
return strides;
154+
}
155+
156+
146157
}//SOFIE
147158
}//Experimental
148159
}//TMVA

0 commit comments

Comments
 (0)