Caffe2 - C++ API
A deep learning, cross platform ML framework
sequence_ops.cc
1 #include "caffe2/operators/sequence_ops.h"
2 #include "caffe2/core/operator.h"
3 #include "caffe2/core/tensor.h"
4 
5 namespace caffe2 {
6 
7 template <>
8 template <typename T>
9 void GatherPaddingOp<CPUContext>::GatherPadding(
10  const int outer_size,
11  const int lengths_size,
12  const int block_size,
13  const int pad_width,
14  const T* in_ptr,
15  const int* lengths_ptr,
16  T* padding_start_ptr,
17  T* padding_end_ptr) {
18  CAFFE_ENFORCE(
19  (!std::is_same<bool, T>::value),
20  "GatherPadding should not be executed on an input of type bool, as "
21  "addition is not properly defined with booleans.");
22  int64_t total_length = 0;
23  for (int i = 0; i < lengths_size; ++i) {
24  // check total length consistency
25  const auto length = lengths_ptr[i];
26  total_length += length;
27  CAFFE_ENFORCE_LE(total_length, outer_size);
28  // accumulate start paddings
29  for (int j = 0; j < startPaddingWidth_; ++j) {
30  for (int k = 0; k < block_size; ++k) {
31  // Note: MSVC warns about unsafe use of type bool in operation.
32  // This is now guarded by a CAFFE_ENFORCE so we can suppress it.
33  #pragma warning(suppress: 4804)
34  padding_start_ptr[k] += in_ptr[k];
35  }
36  in_ptr += block_size;
37  }
38  in_ptr += block_size * (length - pad_width);
39  // accumulate end paddings
40  for (int j = 0; j < endPaddingWidth_; ++j) {
41  for (int k = 0; k < block_size; ++k) {
42  #pragma warning(suppress: 4804)
43  padding_end_ptr[k] += in_ptr[k];
44  }
45  in_ptr += block_size;
46  }
47  }
48 }
49 
50 template <>
51 template <typename T>
52 bool RemovePaddingOp<CPUContext>::DoRunWithType() {
53  const auto& in = Input(0);
54  CAFFE_ENFORCE_GE(in.ndim(), 1);
55  const int32_t outer_size = in.dims()[0];
56  const auto block_size = std::accumulate(
57  in.dims().begin() + 1, in.dims().end(), 1, std::multiplies<TIndex>());
58  const auto pad_width = startPaddingWidth_ + endPaddingWidth_;
59 
60  // if no lengths is provided, assume it is a single full-span entry
61  const int32_t* lengths_ptr = &outer_size;
62  int64_t lengths_size = 1;
63  if (InputSize() > 1) {
64  const auto& lengths = Input(1);
65  lengths_ptr = lengths.data<int32_t>();
66  lengths_size = lengths.size();
67  }
68 
69  auto* out = Output(0);
70  {
71  auto out_dims = in.dims();
72  out_dims[0] -= pad_width * lengths_size;
73  out->Resize(std::move(out_dims));
74  }
75  const auto* in_ptr = in.template data<T>();
76  auto* out_ptr = out->template mutable_data<T>();
77  int64_t total_length = 0;
78  for (int i = 0; i < lengths_size; ++i) {
79  // check that total length is consistent
80  const auto length = lengths_ptr[i];
81  total_length += length;
82  CAFFE_ENFORCE_LE(total_length, outer_size);
83  std::copy(
84  in_ptr + block_size * startPaddingWidth_,
85  in_ptr + block_size * (length - endPaddingWidth_),
86  out_ptr);
87  in_ptr += block_size * length;
88  out_ptr += block_size * (length - pad_width);
89  }
90  if (OutputSize() == 1) {
91  return true;
92  }
93  auto* lengths_out = Output(1);
94  lengths_out->Resize(lengths_size);
95  std::transform(
96  lengths_ptr,
97  lengths_ptr + lengths_size,
98  lengths_out->mutable_data<int32_t>(),
99  [pad_width](int32_t x) { return x - pad_width; });
100  return true;
101 }
102 
103 template <>
104 template <typename T>
105 bool AddPaddingOp<CPUContext>::MakePadding(
106  const T* in_ptr,
107  T* out_ptr,
108  const int32_t* lengths_ptr,
109  int32_t lengths_size,
110  int32_t outer_size,
111  const T* padding_start_ptr,
112  const T* padding_end_ptr,
113  int64_t block_size) {
114  if (!lengths_ptr) {
115  lengths_ptr = &outer_size;
116  }
117 
118  int64_t total_length = 0;
119  for (int i = 0; i < lengths_size; ++i) {
120  // check that total length is consistent
121  const auto length = lengths_ptr[i];
122  total_length += length;
123  CAFFE_ENFORCE_LE(total_length, outer_size);
124  // copy padding before
125  if (!padding_start_ptr) {
126  memset(out_ptr, 0, block_size * startPaddingWidth_ * sizeof(T));
127  out_ptr += block_size * startPaddingWidth_;
128  } else {
129  for (int j = 0; j < startPaddingWidth_; ++j) {
130  std::copy(padding_start_ptr, padding_start_ptr + block_size, out_ptr);
131  out_ptr += block_size;
132  }
133  }
134  // copy payload
135  const auto num_elems = block_size * length;
136  std::copy(in_ptr, in_ptr + num_elems, out_ptr);
137  in_ptr += num_elems;
138  out_ptr += num_elems;
139  // copy padding after
140  if (!padding_end_ptr) {
141  memset(out_ptr, 0, block_size * endPaddingWidth_ * sizeof(T));
142  out_ptr += block_size * endPaddingWidth_;
143  } else {
144  for (int j = 0; j < endPaddingWidth_; ++j) {
145  std::copy(padding_end_ptr, padding_end_ptr + block_size, out_ptr);
146  out_ptr += block_size;
147  }
148  }
149  }
150  if (OutputSize() == 1) {
151  return true;
152  }
153  auto* lengths_out = Output(1);
154  lengths_out->Resize(lengths_size);
155  const auto pad_width = startPaddingWidth_ + endPaddingWidth_;
156  std::transform(
157  lengths_ptr,
158  lengths_ptr + lengths_size,
159  lengths_out->mutable_data<int32_t>(),
160  [pad_width](int32_t x) { return x + pad_width; });
161  return true;
162 }
163 
164 template <>
165 bool PadEmptySamplesOp<CPUContext>::RunOnDevice() {
166  auto& lengths = Input(0);
167  auto* lengthsPtr = lengths.template data<int32_t>();
168  CAFFE_ENFORCE(lengths.ndim() == 1, "LENGTH should be 1-D");
169  CAFFE_ENFORCE(InputSize() >= 1, "Input size must be no less than 1");
170 
171  auto* out_lengths = Output(0);
172  int needPadding = 0;
173  int sumLen = 0;
174  for (int i = 0; i < lengths.size(); ++i) {
175  if (lengthsPtr[i] == 0) {
176  needPadding++;
177  }
178  sumLen += lengthsPtr[i];
179  }
180 
181  out_lengths->Resize(lengths.size());
182  auto* outLengthsPtr = out_lengths->template mutable_data<int32_t>();
183  for (int i = 0; i < lengths.size(); ++i) {
184  if (lengthsPtr[i] == 0) {
185  outLengthsPtr[i] = 1;
186  } else {
187  outLengthsPtr[i] = lengthsPtr[i];
188  }
189  }
190 
191  for (int k = 0; k < InputSize() - 1; k++) {
192  auto& features = Input(1 + k);
193  CAFFE_ENFORCE(features.ndim() >= 1, "FEATURE should at least 1-D");
194  CAFFE_ENFORCE(
195  features.dim(0) == sumLen, "FEATURE and LENGTH should be consistent");
196  const auto block_size = features.size_from_dim(1);
197 
198  auto* out_features = Output(1 + k);
199  auto outDim = features.dims();
200  outDim.at(0) += needPadding;
201  out_features->Resize(outDim);
202  auto dst =
203  static_cast<char*>(out_features->raw_mutable_data(features.meta()));
204  auto src_base = static_cast<const char*>(features.raw_data());
205  // copy data and add padding index as zero
206  Tensor<CPUContext> zero;
207  zero.Resize(block_size);
208  auto zeroPtr =
209  static_cast<const char*>(zero.raw_mutable_data(features.meta()));
210  int start_dest = 0;
211  int start_src = 0;
212  for (int i = 0; i < lengths.size(); ++i) {
213  if (lengthsPtr[i] == 0) {
214  context_.template CopyItems<CPUContext, CPUContext>(
215  features.meta(),
216  block_size,
217  zeroPtr,
218  dst + start_dest * features.meta().itemsize());
219  start_dest += block_size;
220  } else {
221  auto src = src_base + start_src * features.meta().itemsize();
222  context_.template CopyItems<CPUContext, CPUContext>(
223  features.meta(),
224  lengthsPtr[i] * block_size,
225  src,
226  dst + start_dest * features.meta().itemsize());
227  start_src += lengthsPtr[i] * block_size;
228  start_dest += lengthsPtr[i] * block_size;
229  }
230  }
231  }
232  return true;
233 }
234 
235 REGISTER_CPU_OPERATOR(AddPadding, AddPaddingOp<CPUContext>);
236 REGISTER_CPU_OPERATOR(RemovePadding, RemovePaddingOp<CPUContext>);
237 REGISTER_CPU_OPERATOR(GatherPadding, GatherPaddingOp<CPUContext>);
238 REGISTER_CPU_OPERATOR(PadEmptySamples, PadEmptySamplesOp<CPUContext>);
239 
241  using GradientMakerBase::GradientMakerBase;
242  vector<OperatorDef> GetGradientDefs() override {
243  // whether to provide lengths as input to gradient
244  vector<std::string> g_inputs{GO(0)};
245  if (Def().input_size() > 1) {
246  CAFFE_ENFORCE(Def().output_size() > 1);
247  g_inputs.push_back(O(1));
248  }
249 
250  vector<OperatorDef> ops;
251  // gradient on the data
252  ops.push_back(CreateOperatorDef(
253  "RemovePadding", "", g_inputs, vector<string>{GI(0)}));
254  // gradient on the start_padding (and end_padding)
255  if (Def().input_size() >= 3) {
256  std::vector<string> padding_grads{GI(2)};
257  if (Def().input_size() == 4) {
258  padding_grads.push_back(GI(3));
259  }
260  auto g_inputs2 = g_inputs;
261  ops.push_back(
262  CreateOperatorDef("GatherPadding", "", g_inputs2, padding_grads));
263  }
264  return ops;
265  }
266 };
267 REGISTER_GRADIENT(AddPadding, GetAddPaddingGradient);
268 
270  using GradientMakerBase::GradientMakerBase;
271  vector<OperatorDef> GetGradientDefs() override {
272  // whether to provide lengths as input to gradient
273  vector<std::string> g_inputs{GO(0)};
274  if (Def().input_size() > 1) {
275  CAFFE_ENFORCE(Def().output_size() > 1);
276  g_inputs.push_back(O(1));
277  }
278 
279  return SingleGradientDef("AddPadding", "", g_inputs, vector<string>{GI(0)});
280  }
281 };
282 REGISTER_GRADIENT(RemovePadding, GetRemovePaddingGradient);
283 
284 OPERATOR_SCHEMA(AddPadding)
285  .NumInputs(1, 4)
286  .NumOutputs(1, 2)
287  .SetDoc(R"DOC(
288 Given a partitioned tensor T<N, D1..., Dn>, where the partitions are
289 defined as ranges on its outer-most (slowest varying) dimension N,
290 with given range lengths, return a tensor T<N + 2*padding_width, D1 ..., Dn>
291 with paddings added to the start and end of each range.
292 Optionally, different paddings can be provided for beginning and end. Paddings
293 provided must be a tensor T<D1..., Dn>.
294 
295 If no padding is provided, add zero padding.
296 If no lengths vector is provided, add padding only once,
297 at the start and end of data.
298 )DOC")
299  .Arg(
300  "padding_width",
301  "Number of copies of padding to add around each range.")
302  .Arg(
303  "end_padding_width",
304  "(Optional) Specifies a different end-padding width.")
305  .Input(0, "data_in", "(T<N, D1..., Dn>) Input data")
306  .Input(
307  1,
308  "lengths",
309  "(i64) Num of elements in each range. sum(lengths) = N.")
310  .Input(2, "start_padding", "T<D1..., Dn> Padding data for range start.")
311  .Input(
312  3,
313  "end_padding",
314  "T<D1..., Dn> (optional) Padding for range end. "
315  "If not provided, start_padding is used as end_padding as well.")
316  .Output(0, "data_out", "(T<N + 2*padding_width, D1..., Dn>) Padded data.")
317  .Output(1, "lengths_out", "(i64, optional) Lengths for each padded range.");
318 
319 OPERATOR_SCHEMA(RemovePadding)
320  .NumInputs(1, 2)
321  .NumOutputs(1, 2)
322  .SetDoc(R"DOC(
323 Remove padding around the edges of each segment of the input data. This is
324 the reverse opration of AddPadding, and uses the same arguments and conventions
325 for input and output data format.
326 )DOC")
327  .Arg("padding_width", "Outer-size of padding to remove around each range.")
328  .Arg(
329  "end_padding_width",
330  "(Optional) Specifies a different end-padding width.")
331  .Input(0, "data_in", "T<N, D1..., Dn> Input data")
332  .Input(
333  1,
334  "lengths",
335  "(i64) Num of elements in each range. sum(lengths) = N. "
336  "If not provided, considers all data as a single segment.")
337  .Output(0, "data_out", "(T<N - 2*padding_width, D1..., Dn>) Unpadded data.")
338  .Output(
339  1,
340  "lengths_out",
341  "(i64, optional) Lengths for each unpadded range.");
342 
343 OPERATOR_SCHEMA(GatherPadding)
344  .NumInputs(2)
345  .NumOutputs(1, 2)
346  .SetDoc(R"DOC(
347 Gather the sum of start and end paddings in a padded input sequence. Used in
348 order to compute the gradients of AddPadding w.r.t the padding tensors.
349 )DOC")
350  .Arg("padding_width", "Outer-size of padding present around each range.")
351  .Arg(
352  "end_padding_width",
353  "(Optional) Specifies a different end-padding width.")
354  .Input(0, "data_in", "T<N, D1..., Dn> Padded input data")
355  .Input(
356  1,
357  "lengths",
358  "(i64) Num of elements in each range. sum(lengths) = N. "
359  "If not provided, considers all data as a single segment.")
360  .Output(
361  0,
362  "padding_sum",
363  "Sum of all start paddings, or of all "
364  "paddings if end_padding_sum is not provided.")
365  .Output(
366  1,
367  "end_padding_sum",
368  "T<D1..., Dn> Sum of all end paddings, if provided.");
369 
370 OPERATOR_SCHEMA(PadEmptySamples)
371  .NumInputs(1, INT_MAX)
372  .NumOutputs(1, INT_MAX)
373  .SetDoc(R"DOC(
374 Pad empty field given lengths and index features,
375 
376 Input(0) is a blob pointing to the lengths of samples in one batch,
377 [Input(1),... Input(num_fields)] a list of tensors containing the data for
378 each field of the features.
379 
380 PadEmptySamples is thread safe.
381 )DOC")
382  .Input(0, "lengths", "A blob containing a pointer to the lengths.")
383  .Output(
384  0,
385  "out_lengths",
386  "Tensor containing lengths with empty sample padded.");
387 
388 } // namespace caffe2
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
static vector< OperatorDef > SingleGradientDef(const Args &...args)
a helper function to allow one to create one single operator def, which is usually the case for many ...