1 #include "caffe2/operators/concat_split_op.h" 5 std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>> splitOpDevInfer(
6 const OperatorDef& def) {
8 def.has_device_option() ? def.device_option() : DeviceOption();
9 vector<DeviceOption> in_dev(def.input_size(), op_device);
10 vector<DeviceOption> out_dev(def.output_size(), op_device);
13 if (def.input_size() == SplitOp<CPUContext>::kSplitOpInputSize) {
14 CAFFE_ENFORCE_GT(in_dev.size(), 1);
15 in_dev[1] = DeviceOption();
17 return std::make_pair(in_dev, out_dev);
21 REGISTER_CPU_OPERATOR(Split, SplitOp<CPUContext>);
22 REGISTER_CPU_OPERATOR(SplitByLengths, SplitByLengthsOp<CPUContext>);
23 OPERATOR_SCHEMA(Split)
25 .NumOutputs(1, INT_MAX)
26 .Input(0,
"input",
"(*Tensor*): tensor to split")
30 "(*Tensor`<int>`*): [OPTIONAL] list of output lengths (see also arg `split`)")
31 .Arg(
"axis",
"(*int*): axis to split on")
32 .Arg(
"split",
"(*Tuple(int)*): length of each output")
35 "(*string*): order of dimensions of input and output blobs; either \"NCHW\" or \"NHWC\"")
36 .Output(0,
"[output_0, output_1, ...]",
"(*Tensor*): output tensor")
37 .DeviceInferenceFunction(splitOpDevInfer)
39 Split an `input` tensor into a list of tensors, along the axis specified by the `axis` dimension. The lengths of the split can be specified using argument `split` or optional second input blob to the operator. Otherwise, the tensor is split to equal sized parts. 42 - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/concat_split_op.cc 46 <summary> <b>Example</b> </summary> 52 workspace.ResetWorkspace() 54 op = core.CreateOperator( 57 ["output_0","output_1","output_2"], 62 workspace.FeedBlob("input", np.random.randint(10, size=(9))) 63 print("input:", workspace.FetchBlob("input")) 64 workspace.RunOperatorOnce(op) 65 print("output_0:", workspace.FetchBlob("output_0")) 66 print("output_1:", workspace.FetchBlob("output_1")) 67 print("output_2:", workspace.FetchBlob("output_2")) 75 input: [2 2 6 6 6 0 5 7 4] 87 OPERATOR_SCHEMA(SplitByLengths) 89 .NumOutputs(1, INT_MAX) 90 .Input(0, "input",
"The tensor to split")
91 .Input(1,
"legnths",
"The tensor `l_i` indicates the logic block of input.")
92 .Arg(
"axis",
"Which axis to split on")
93 .Arg(
"order",
"Either NHWC or NCWH, will split on C axis, defaults to NCHW")
94 .DeviceInferenceFunction([](
const OperatorDef& def) {
96 def.has_device_option() ? def.device_option() : DeviceOption();
97 vector<DeviceOption> in_dev(def.input_size(), op_device);
98 vector<DeviceOption> out_dev(def.output_size(), op_device);
100 in_dev[1] = DeviceOption();
101 return std::make_pair(in_dev, out_dev);
104 Split a tensor into a list of tensors, given a lengths input, along the specified 105 'axis'. If `K` outputs are provided, the op assumes `len(lengths) % K == 0`. 106 The `input` will be split into `K` parts. Each part of length 107 `sum(lengths[i*k:i*k+k))`)DOC"); 109 OpSchema::Cost CostInferenceForConcat( 110 const OperatorDef& def,
111 const vector<TensorShape>& in) {
112 ArgumentHelper helper(def);
113 const int axis = helper.HasArgument(
"axis")
114 ? helper.GetSingleArgument<
int>(
"axis", -1)
115 : GetDimFromOrderString(
116 helper.GetSingleArgument<
string>(
"order",
"NCHW"));
117 bool add_axis = helper.GetSingleArgument<
int>(
"add_axis", 0) != 0;
118 const int canonical_axis = canonical_axis_index_(axis, in[0].dims_size());
119 CAFFE_ENFORCE_GT(in.size(), 0);
120 vector<int> out_shape(in[0].dims().begin(), in[0].dims().end());
122 out_shape.insert(out_shape.begin() + canonical_axis, in.size());
124 for (
int i = 1; i < in.size(); ++i) {
125 out_shape[canonical_axis] += in[i].dims(canonical_axis);
128 uint64_t nElemRead = 1;
129 for (
int i = 0; i < in.size(); ++i) {
130 nElemRead += nElemFromDim(in[i]);
133 for (
auto& s : out_shape) {
137 struct OpSchema::Cost cost;
139 cost.bytes_read = nElemRead *
sizeof(in[0].data_type());
140 cost.bytes_written = size *
sizeof(in[0].data_type());
141 cost.params_bytes = 0;
146 std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>
147 concatOpDevInfer(
const OperatorDef& def) {
149 def.has_device_option() ? def.device_option() : DeviceOption();
150 vector<DeviceOption> in_dev(def.input_size(), op_device);
151 vector<DeviceOption> out_dev(def.output_size(), op_device);
154 CAFFE_ENFORCE_GT(out_dev.size(), 1);
155 out_dev[1] = DeviceOption();
156 return std::make_pair(in_dev, out_dev);
160 vector<TensorShape> TensorInferenceForConcat(
161 const OperatorDef& def,
162 const vector<TensorShape>& in) {
163 ArgumentHelper helper(def);
164 const int axis = helper.HasArgument(
"axis")
165 ? helper.GetSingleArgument<
int>(
"axis", -1)
166 : GetDimFromOrderString(
167 helper.GetSingleArgument<
string>(
"order",
"NCHW"));
168 bool add_axis = helper.GetSingleArgument<
int>(
"add_axis", 0) != 0;
169 int adj_size = in[0].dims_size() + (add_axis ? 1 : 0);
170 const int canonical_axis = canonical_axis_index_(axis, adj_size);
171 CAFFE_ENFORCE_LT(canonical_axis, adj_size,
"Axis not in input ndim range.");
172 CAFFE_ENFORCE_GT(in.size(), 0);
173 vector<int> split_shape(1, in.size());
174 vector<int> out_shape(in[0].dims().begin(), in[0].dims().end());
176 for (
int i = 1; i < in.size(); ++i) {
180 "All inputs of Concat should have same dims when add_axis = 1. " 181 "Got different sizes for inputs 0 and ",
183 for (
int j = 0; j < in[0].dims().size(); ++j) {
187 "All inputs of Concat should have same dims when add_axis = 1. " 188 "Got different dims for inputs 0 and ",
194 out_shape.insert(out_shape.begin() + canonical_axis, in.size());
196 for (
int i = 1; i < in.size(); ++i) {
200 "All inputs of Concat should have same dims except " 201 "canonical_axis dim that is equal to ",
203 "Got different sizes for inputs 0 and ",
205 for (
int j = 0; j < in[0].dims().size(); ++j) {
206 if (j == canonical_axis) {
212 "All inputs of Concat should have same dims except " 213 "canonical_axis dim that is equal to ",
215 "Got different dims for inputs 0 and ",
222 for (
int i = 1; i < in.size(); ++i) {
223 out_shape[canonical_axis] += in[i].dims(canonical_axis);
226 if (def.output_size() == 1) {
227 return vector<TensorShape>{CreateTensorShape(out_shape, in[0].data_type())};
229 return vector<TensorShape>{
230 CreateTensorShape(out_shape, in[0].data_type()),
231 CreateTensorShape(split_shape, TensorProto::INT32)};
234 REGISTER_CPU_OPERATOR(
Concat, ConcatOp<CPUContext>);
236 .NumInputs(1, INT_MAX)
238 .Arg(
"axis",
"*(type: int; default: -1)* Axis to concatenate on.")
241 "*(type: string; default='NCHW')* Order of blob dimensions. Concats on the C dimension.")
244 "*(type: int)* Pass non-zero integer to add the axis specified in `axis` to all input tensors.")
245 .TensorInferenceFunction(
247 .CostInferenceFunction(CostInferenceForConcat)
248 .DeviceInferenceFunction(concatOpDevInfer)
250 Concatenate a list of tensors into a single tensor. Similar functionality to 251 Numpy's [concatenate](https://docs.scipy.org/doc/numpy/reference/generated/numpy.concatenate.html) 252 function. The `axis` argument specifies what axis along which the arrays will be concatenated. 253 When set to non-zero (default=0), the `add_axis` argument adds the axis specified in `axis` to 258 - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/concat_split_op.cc 259 - https://github.com/pytorch/pytorch/blob/master/caffe2/operators/concat_split_op.h 264 <summary> <b>Example</b> </summary> 270 workspace.ResetWorkspace() 272 op = core.CreateOperator( 279 workspace.FeedBlob("X1", np.array([[1,2],[3,4]])) 280 workspace.FeedBlob("X2", np.array([[5,6]])) 281 print("X1:", workspace.FetchBlob("X1")) 282 print("X2:", workspace.FetchBlob("X2")) 283 workspace.RunOperatorOnce(op) 284 print("Y:", workspace.FetchBlob("Y")) 285 print("split_info:", workspace.FetchBlob("split_info")) 307 <summary> <b>Example 2</b> </summary> 313 workspace.ResetWorkspace() 315 op = core.CreateOperator( 323 workspace.FeedBlob("X1", np.random.randint(10, size=(1, 1, 5, 5))) // NCHW 324 workspace.FeedBlob("X2", np.random.randint(10, size=(1, 1, 5, 5))) // NCHW 325 print("X1:", workspace.FetchBlob("X1")) 326 print("X2:", workspace.FetchBlob("X2")) 327 workspace.RunOperatorOnce(op) 328 print("Y:", workspace.FetchBlob("Y")) 329 print("split_info:", workspace.FetchBlob("split_info")) 368 .Input(0, "X1, X2, ...",
"*(type: Tensor`<float>`)* List of input tensors.")
372 "*(type: Tensor`<float>`)* Concatenated tensor.")
376 "*(type: Tensor`<int>`)* The dimensions of the inputs.")
377 .InheritOnnxSchema();
380 REGISTER_CPU_OPERATOR(DepthSplit, SplitOp<CPUContext>);
381 REGISTER_CPU_OPERATOR(DepthConcat, ConcatOp<CPUContext>);
382 OPERATOR_SCHEMA(DepthSplit)
384 .NumOutputs(1, INT_MAX)
385 .SetDoc(
"Backward compatible operator name for Split.");
386 OPERATOR_SCHEMA(DepthConcat)
387 .NumInputs(1, INT_MAX)
389 .SetDoc(
"Backward compatible operator name for Concat.");
391 class GetSplitGradient :
public GradientMakerBase {
392 using GradientMakerBase::GradientMakerBase;
393 vector<OperatorDef> GetGradientDefs()
override {
394 vector<string> output_grads;
395 for (
int i = 0; i < def_.output_size(); ++i) {
396 if (!GradOut(i).IsEmpty()) {
397 output_grads.push_back(GO(i));
400 if (output_grads.empty()) {
403 return SingleGradientDef(
407 vector<string>{GI(0),
"_" + GI(0) +
"_dims"});
410 REGISTER_GRADIENT(Split, GetSplitGradient);
411 REGISTER_GRADIENT(DepthSplit, GetSplitGradient);
412 REGISTER_GRADIENT(SplitByLengths, GetSplitGradient);
414 class GetConcatGradient :
public GradientMakerBase {
415 using GradientMakerBase::GradientMakerBase;
416 vector<OperatorDef> GetGradientDefs()
override {
417 if (GradOut(0).IsEmpty()) {
420 vector<string> grads;
421 for (
int i = 0; i < def_.input_size(); ++i) {
422 grads.push_back(GI(i));
424 return SingleGradientDef(
"Split",
"", vector<string>{GO(0), O(1)}, grads);
427 REGISTER_GRADIENT(
Concat, GetConcatGradient);
428 REGISTER_GRADIENT(DepthConcat, GetConcatGradient);
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
static TensorInferenceFunctionType NeedsAllInputShapes(TensorInferenceFunctionType f)
A wrapper that makes an infer tensor function to return unknown shape for all outputs if any one of t...