1 #include "caffe2/operators/boolean_mask_ops.h" 2 #include "caffe2/core/operator.h" 3 #include "caffe2/core/tensor.h" 8 template <
class Context>
9 class BooleanMaskLengthsOp final :
public Operator<Context> {
11 USE_OPERATOR_CONTEXT_FUNCTIONS;
12 BooleanMaskLengthsOp(
const OperatorDef& operator_def, Workspace* ws)
13 : Operator<Context>(operator_def, ws) {}
15 bool RunOnDevice()
override {
16 return DispatchHelper<TensorTypes<int32_t, int64_t>>::call(
this, Input(0));
20 bool DoRunWithType() {
21 auto& lengths = Input(0);
22 auto& mask = Input(1);
23 auto* lengthsOut = Output(0);
24 CAFFE_ENFORCE(lengths.ndim() == 1);
25 CAFFE_ENFORCE(mask.ndim() == 1);
26 const auto* lengthsPtr = lengths.template data<T>();
27 const auto* maskPtr = mask.template data<bool>();
29 std::accumulate(lengthsPtr, lengthsPtr + lengths.size(), 0);
30 CAFFE_ENFORCE(mask.size() == totalLength);
31 lengthsOut->ResizeLike(lengths);
32 auto* lengthsOutPtr = lengthsOut->template mutable_data<T>();
34 for (
int i = 0; i < lengths.size(); ++i) {
36 for (
int j = 0; j < lengthsPtr[i]; ++j) {
41 lengthsOutPtr[i] = lengthOut;
49 bool BooleanMaskOp<CPUContext>::RunOnDevice() {
50 auto& data = Input(0);
51 auto& mask = Input(1);
52 auto* dataOut = Output(0);
53 CAFFE_ENFORCE(data.ndim() >= 1);
54 CAFFE_ENFORCE_EQ(mask.ndim(), 1);
55 CAFFE_ENFORCE(data.dims()[0] == mask.dims()[0]);
57 const auto* maskPtr = mask.template data<bool>();
59 int outerSize = mask.size();
60 for (
int i = 0; i < outerSize; ++i) {
65 std::vector<TIndex> outShape;
66 outShape.push_back(numOutputs);
67 outShape.insert(outShape.end(), data.dims().begin() + 1, data.dims().end());
68 dataOut->Resize(outShape);
69 auto* outPtr = (
char*)dataOut->raw_mutable_data(data.meta());
71 int64_t* out_vec =
nullptr;
72 if (OutputSize() == 2) {
73 auto* indicesOut = Output(1);
74 indicesOut->Resize(numOutputs);
75 out_vec = indicesOut->template mutable_data<int64_t>();
78 if (numOutputs == 0) {
81 const auto innerSize = data.size_from_dim(1);
82 const auto innerSizeBytes = innerSize * data.meta().itemsize();
84 TIndex lastStart = -1;
85 const auto* inPtr = (
char*)data.raw_data();
88 for (TIndex i = 0;; ++i) {
90 if (lastStart != -1 && ((i >= outerSize) || !maskPtr[i])) {
91 const auto* src = inPtr + lastStart * innerSizeBytes;
92 auto* dst = outPtr + outStart * innerSizeBytes;
93 int numItems = i - lastStart;
94 context_.template CopyItems<CPUContext, CPUContext>(
95 data.meta(), numItems * innerSize, src, dst);
103 if (lastStart == -1 && maskPtr[i]) {
106 if (maskPtr[i] && OutputSize() == 2) {
113 REGISTER_CPU_OPERATOR(BooleanMask, BooleanMaskOp<CPUContext>);
114 REGISTER_CPU_OPERATOR(BooleanMaskLengths, BooleanMaskLengthsOp<CPUContext>);
116 OPERATOR_SCHEMA(BooleanMask)
120 Given a data tensor and a 1D boolean mask tensor, returns a tensor containing 121 only the elements corresponding to positions where the mask is true. 123 .Input(0, "data",
"The 1D, original data tensor.")
124 .Input(1,
"mask",
"A tensor of bools of same shape as `data`.")
125 .Output(0,
"masked_data",
"A tensor of same type as `data`.")
126 .Output(1,
"masked_indices",
"A tensor for indices.");
128 OPERATOR_SCHEMA(BooleanMaskLengths)
132 Given a tensor of int32 segment lengths and a mask (boolean) tensor, return 133 the segment lengths of a corresponding segmented tensor after BooleanMask is 136 .Input(0, "lengths",
"A 1D int32 tensor representing segment lengths.")
137 .Input(1,
"mask",
"A 1D bool tensor of values to keep.")
138 .Output(0,
"masked_lengths",
"Segment lengths of a masked tensor.");
140 NO_GRADIENT(BooleanMask)
141 NO_GRADIENT(BooleanMaskLengths);
143 const
float minf = -1.0f *
std::numeric_limits<
float>::infinity();
147 template <typename Functor>
148 void MaskWithFunctor(
162 for (
int i = 0; i < B; ++i) {
163 for (
int j = 0; j < N; ++j) {
164 for (
int k = 0; k < M; ++k) {
168 auto val = in[N * M * i + M * j + k];
169 out[N * M * i + M * j + k] = (fn(j, k, val) ? fill_val : val);
179 for (
int i = 0; i < N; ++i) {
180 for (
int j = 0; j < M; ++j) {
181 auto val = in[M * i + j];
182 out[M * i + j] = (fn(i, j, val) ? fill_val : val);
189 template <
typename Functor>
190 void RepeatedMaskWithFunctor(
198 for (
int i = 0; i < N; ++i) {
199 for (
int j = 0; j < M; ++j) {
200 for (
int k = 0; k < D; ++k) {
201 auto val = in[M * D * i + D * j + k];
202 out[M * D * i + D * j + k] = (fn(i, j, val) ? fill_val : val);
210 class SequenceFunctor {
212 explicit SequenceFunctor(
const int* sl,
const size_t len)
213 : sl_(sl), len_(len) {}
214 bool operator()(
int i,
int j,
float ) {
215 CAFFE_ENFORCE(i < len_,
"Out of bound.");
224 class WindowFunctor {
226 explicit WindowFunctor(
const int* c,
int r) : c(c), r(r) {}
227 bool operator()(
int i,
int j,
float ) {
228 return j > c[i] + r || j < c[i] - r;
238 bool operator()(
int i,
int j,
float ) {
245 bool operator()(
int i,
int j,
float ) {
250 class UpperDiagFunctor {
252 bool operator()(
int i,
int j,
float ) {
257 class LowerDiagFunctor {
259 bool operator()(
int i,
int j,
float ) {
267 bool SequenceMaskOp<CPUContext>::RunOnDevice() {
268 return DispatchHelper<TensorTypes<float>>::call(
this, Input(0));
273 bool SequenceMaskOp<CPUContext>::DoRunWithType() {
278 if (mode_ ==
"sequence") {
279 sequence_lengths = &Input(1);
280 }
else if (mode_ ==
"window") {
281 window_centers = &Input(1);
284 auto* output = Output(0);
285 output->ResizeLike(*input);
287 const auto canonical_axis = input->canonical_axis_index(axis_);
290 int canonical_batch = -1;
291 if ((HasArgument(
"batch"))) {
292 canonical_batch = input->canonical_axis_index(batch_);
296 if (canonical_batch >= 0) {
297 CAFFE_ENFORCE_LT(canonical_batch, canonical_axis);
303 (canonical_batch >= 0
304 ? input->size_between_dim(canonical_batch, canonical_axis)
305 : input->size_to_dim(canonical_axis));
306 const int right = input->size_from_dim(canonical_axis);
309 const int batch_dim =
310 (canonical_batch >= 0
311 ? input->size_to_dim(canonical_batch) * input->dim(canonical_batch)
314 T fill_val = convert::To<float, T>(grad_ ? 0.0f : fill_val_);
315 if (mode_ ==
"sequence") {
317 sequence_lengths,
"Sequence length not provided for mode 'sequence'!");
318 if (HasArgument(
"repeat_from_axis")) {
319 const int canonical_repeat_from =
320 input->canonical_axis_index(repeat_from_);
321 const int repeated_dims = input->size_from_dim(canonical_repeat_from);
322 const int masked_dims = right / repeated_dims;
323 RepeatedMaskWithFunctor(
329 sequence_lengths->data<
int>(), sequence_lengths->size()),
331 output->mutable_data<T>());
339 sequence_lengths->data<
int>(), sequence_lengths->size()),
341 output->mutable_data<T>());
343 }
else if (mode_ ==
"window") {
349 WindowFunctor(window_centers->data<
int>(), radius_),
351 output->mutable_data<T>());
352 }
else if (mode_ ==
"upper") {
360 output->mutable_data<T>());
361 }
else if (mode_ ==
"lower") {
369 output->mutable_data<T>());
370 }
else if (mode_ ==
"upperdiag") {
378 output->mutable_data<T>());
379 }
else if (mode_ ==
"lowerdiag") {
387 output->mutable_data<T>());
389 CAFFE_ENFORCE(
false,
"Unsupported mode for SequenceMaskOp!");
396 REGISTER_CPU_OPERATOR(SequenceMask, SequenceMaskOp<CPUContext>);
398 OPERATOR_SCHEMA(SequenceMask)
402 Mask op designed for use in attention mechanisms for sequence modeling tasks. 403 Supports batching: given batch_dim, collapses dims 0 through batch_dim into a 404 single dimension, e.g. if tensor dims are [4,2,1,3,4] and batch_dim=2, first 405 collapse tensor to [4*2*1,3,4], then mask each batch [i,:,:]. 408 Two current operating modes: 411 1) Given a 2D input tensor and 1D tensor of sequence lengths, for each row i in 412 the input tensor, set elements in that row to -inf if their column index 413 j >= sequence_lengths[i]. This mode takes two inputs and argument mode = 417 2) Triangular mask. Given row index i and column index j, set elements to -inf 418 given the following conditions: 420 mode='upper', x_ij = -inf if j < i 421 mode='lower', x_ij = -inf if j > i 422 mode='upperdiag', x_ij = -inf if j <= i 423 mode='lowerdiag', x_ij = -inf if j >= i 425 This mode takes one input. 428 3) Window Mask. Given a 2D input tensor and 1D tensor of window centers, 429 for each row i in the input tensor, set elements in that row to -inf 430 if their column index j outside [center - radius, center + radius]. 431 This mode takes two inputs and argument mode = 'sequence'. 432 Argument 'radius' should be provided. 434 .Input(0, "input",
"Tensor to apply masking to")
435 .Input(1,
"sequence_lengths",
"1D Tensor of sequence lengths for mode #1")
436 .Output(0,
"masked_tensor",
"Input tensor with masking applied")
439 "(string) Mode selection. Possible values: " 440 "'sequence', 'upper', 'lower', 'upperdiag', 'lowerdiag'")
443 "(int) Beginning axis of row elements. All dimensions to the left " 444 "will be treated as row indices and those to the right (inclusive) " 445 "will be treated as column indices in the 2D mask")
446 .Arg(
"grad",
"(bool) operate in gradient mode")
447 .Arg(
"radius",
"(int) radius of windows in window mode")
448 .Arg(
"batch",
"(int) batch dimension of tensor (optional)")
451 "(int) used when mask should be repeated for " 452 "one or more data dimensions (beginning at this axis). " 453 "(currently only supported for sequence mode without batch argument)");
455 class GetSequenceMaskGradient :
public GradientMakerBase {
456 using GradientMakerBase::GradientMakerBase;
457 vector<OperatorDef> GetGradientDefs()
override {
458 vector<Argument> args;
459 args.reserve(Def().arg().size());
460 for (
const auto& x : Def().arg()) {
463 args.push_back(MakeArgument<bool>(
"grad",
true));
464 if (def_.input_size() == 1) {
465 return SingleGradientDef(
468 vector<string>{GO(0)},
469 vector<string>{GI(0)},
472 return SingleGradientDef(
475 vector<string>{GO(0), I(1)},
476 vector<string>{GI(0)},
481 bool CopyArguments()
const override {
486 REGISTER_GRADIENT(SequenceMask, GetSequenceMaskGradient);
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...