-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add sequence slice operator #5546
Changes from 7 commits
23b0388
f23d6cc
b24afd8
8e7c8bb
29c2582
9a18e78
4c426f1
40a6c48
3dcf0da
1d95173
7bb2680
4579bd4
352c5a9
069dcc2
49a5942
294570f
a0b7a07
06dc89e
3603546
39bce49
794117b
f7c1562
d68f861
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. */ | ||
|
||
#include "paddle/operators/sequence_slice_op.h" | ||
|
||
namespace paddle { | ||
namespace operators { | ||
|
||
class SequenceSliceOp : public framework::OperatorWithKernel { | ||
public: | ||
using framework::OperatorWithKernel::OperatorWithKernel; | ||
|
||
void InferShape(framework::InferShapeContext* ctx) const override { | ||
PADDLE_ENFORCE(ctx->HasInput("X"), | ||
"Input(X) of SequenceSliceOp should not be null."); | ||
PADDLE_ENFORCE(ctx->HasInput("Offset"), | ||
"Input(Offset) of SequenceSliceOp should not be null."); | ||
PADDLE_ENFORCE(ctx->HasInput("Length"), | ||
"Input(Length) of SequenceSliceOp should not be null."); | ||
PADDLE_ENFORCE(ctx->HasOutput("Out"), | ||
"Output(Out) of SequenceSliceOp should not be null."); | ||
auto input_dims = ctx->GetInputDim("X"); | ||
|
||
ctx->SetOutputDim("Out", input_dims); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add some notes (why do you set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If not set, it triggers an error so initialize the output dims by set input_dims to the Out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} | ||
|
||
protected: | ||
framework::OpKernelType GetKernelType( | ||
const framework::ExecutionContext& ctx) const override { | ||
return framework::OpKernelType( | ||
framework::ToDataType(ctx.Input<framework::LoDTensor>("X")->type()), | ||
ctx.device_context()); | ||
} | ||
}; | ||
|
||
class SequenceSliceGradOp : public framework::OperatorWithKernel { | ||
public: | ||
using framework::OperatorWithKernel::OperatorWithKernel; | ||
|
||
void InferShape(framework::InferShapeContext* ctx) const override { | ||
PADDLE_ENFORCE(ctx->HasInput(framework::GradVarName("Out")), | ||
"The gradient of Out should not be null."); | ||
PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName("X")), | ||
"The gradient of X should not be null."); | ||
ctx->SetOutputsDim(framework::GradVarName("X"), ctx->GetInputsDim("X")); | ||
} | ||
|
||
protected: | ||
framework::OpKernelType GetKernelType( | ||
const framework::ExecutionContext& ctx) const override { | ||
return framework::OpKernelType( | ||
framework::ToDataType(ctx.Input<framework::LoDTensor>("X")->type()), | ||
ctx.device_context()); | ||
} | ||
}; | ||
|
||
class SequenceSliceOpMaker : public framework::OpProtoAndCheckerMaker { | ||
public: | ||
SequenceSliceOpMaker(framework::OpProto* proto, | ||
framework::OpAttrChecker* op_checker) | ||
: OpProtoAndCheckerMaker(proto, op_checker) { | ||
AddInput("X", | ||
"(LoDTensor), " | ||
"the input of SequenceSliceOp."); | ||
AddInput("Offset", | ||
"(Tensor), " | ||
"a vector<int> to describe the offset of every input sequence for " | ||
"sub sequence item."); | ||
AddInput("Length", | ||
"(Tensor), " | ||
"a vector<int> to describe the length of every input sequence for " | ||
"sub sequence item."); | ||
AddOutput("Out", | ||
"(LoDTensor), The output of SequenceSliceOp."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
AddComment(R"DOC( | ||
Sequence slice operator | ||
|
||
The operator crop a subsequence from given sequence with given start offset and subsequence length. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be a blank row below "Sequence slice operator" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for your reference only. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
It only supports sequence (LoD Tensor with level number is 1). | ||
- Case: | ||
X = [[a1, a2; | ||
b1, b2; | ||
c1, c2] | ||
[d1, d2; | ||
e1, e2]] | ||
LoD(X) = {{0, 3, 5}}; Dims(X) = (5, 2) | ||
Offset = [0, 1]; Length = [2, 1] | ||
|
||
Out = [[a1, a2; | ||
b1, b2] | ||
[e1, e2]] | ||
LoD(Out) = {{0, 2, 3}}; Dims(Out) = (3, 2) | ||
NOTE: The length of the input, offset and length should be the same. The offset start from 0. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for your reference only. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
)DOC"); | ||
} | ||
}; | ||
|
||
} // namespace operators | ||
} // namespace paddle | ||
|
||
namespace ops = paddle::operators; | ||
REGISTER_OP(sequence_slice, ops::SequenceSliceOp, ops::SequenceSliceOpMaker, | ||
sequence_slice_grad, ops::SequenceSliceGradOp); | ||
REGISTER_OP_CPU_KERNEL( | ||
sequence_slice, | ||
ops::SequenceSliceOpKernel<paddle::platform::CPUPlace, float>); | ||
REGISTER_OP_CPU_KERNEL( | ||
sequence_slice_grad, | ||
ops::SequenceSliceGradOpKernel<paddle::platform::CPUPlace, float>); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. */ | ||
|
||
#include "paddle/operators/sequence_slice_op.h" | ||
|
||
namespace ops = paddle::operators; | ||
REGISTER_OP_GPU_KERNEL( | ||
sequence_slice, | ||
ops::SequenceSliceOpKernel<paddle::platform::GPUPlace, float>); | ||
REGISTER_OP_GPU_KERNEL( | ||
sequence_slice_grad, | ||
ops::SequenceSliceGradOpKernel<paddle::platform::GPUPlace, float>); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. */ | ||
|
||
#pragma once | ||
#include "paddle/framework/op_registry.h" | ||
#include "paddle/operators/math/math_function.h" | ||
#include "paddle/operators/strided_memcpy.h" | ||
|
||
namespace paddle { | ||
namespace operators { | ||
|
||
using Tensor = framework::Tensor; | ||
using LoDTensor = framework::LoDTensor; | ||
using LoD = framework::LoD; | ||
|
||
template <typename T> | ||
inline LoD SequenceSliceLoD(const T& in, const int64_t* offset_data, | ||
const int64_t* length_data) { | ||
auto out_lod = in.lod(); | ||
size_t lod_offset = 0; | ||
|
||
auto n = in.lod()[0].size() - 1; | ||
out_lod[0][0] = 0; | ||
for (size_t i = 0; i < n; ++i) { | ||
lod_offset += length_data[i]; | ||
out_lod[0][i+1] = lod_offset; | ||
} | ||
return out_lod; | ||
} | ||
|
||
template <typename Place, typename T> | ||
class SequenceSliceOpKernel : public framework::OpKernel<T> { | ||
public: | ||
void Compute(const framework::ExecutionContext& ctx) const override { | ||
auto* in = ctx.Input<LoDTensor>("X"); | ||
auto* offset = ctx.Input<Tensor>("Offset"); | ||
auto* length = ctx.Input<Tensor>("Length"); | ||
auto* out = ctx.Output<LoDTensor>("Out"); | ||
|
||
const int64_t* offset_data = offset->data<int64_t>(); | ||
const int64_t* length_data = length->data<int64_t>(); | ||
|
||
if (platform::is_gpu_place(ctx.GetPlace())) { | ||
framework::Tensor offset_cpu; | ||
offset_cpu.mutable_data<T>(offset->dims(), platform::CPUPlace()); | ||
offset_cpu.CopyFrom(*offset, platform::CPUPlace(), ctx.device_context()); | ||
offset_data = offset_cpu.data<int64_t>(); | ||
|
||
framework::Tensor length_cpu; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these two variables be placed outside of the if_block? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what the difference? |
||
length_cpu.mutable_data<T>(length->dims(), platform::CPUPlace()); | ||
length_cpu.CopyFrom(*length, platform::CPUPlace(), ctx.device_context()); | ||
length_data = length_cpu.data<int64_t>(); | ||
} | ||
|
||
auto lod = in->lod(); | ||
auto n = lod[0].size() - 1; | ||
|
||
PADDLE_ENFORCE_EQ(lod.size(), 1UL, "Only support one level sequence now."); | ||
PADDLE_ENFORCE_EQ(offset->dims().size(), 1UL, | ||
"Only support one level sequence now."); | ||
PADDLE_ENFORCE_EQ(length->dims().size(), 1UL, | ||
"Only support one level sequence now."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
PADDLE_ENFORCE_EQ( | ||
n, length->dims()[0], | ||
"The size of input-sequence and length-array should be the same") | ||
PADDLE_ENFORCE_EQ( | ||
n, offset->dims()[0], | ||
"The size of input-sequence and offset-array should be the same") | ||
|
||
for (size_t i = 0; i < n; ++i) { | ||
PADDLE_ENFORCE_LT(0, offset_data[i], "The offset must greater than zero") | ||
PADDLE_ENFORCE_LT(0, length_data[i], "The length must greater than zero") | ||
PADDLE_ENFORCE_LT(lod[0][i] + offset_data[i] + length_data[i], | ||
lod[0][i + 1], "The target tensor's length overflow") | ||
} | ||
|
||
out->mutable_data<T>(ctx.GetPlace()); | ||
auto out_lod = SequenceSliceLoD(*in, offset_data, length_data); | ||
auto out_dims = in->dims(); | ||
out_dims[0] = out_lod[0][out_lod[0].size() - 1]; | ||
out->Resize(out_dims); | ||
out->set_lod(out_lod); | ||
|
||
auto in_stride = framework::stride(in->dims()); | ||
auto out_stride = framework::stride(out->dims()); | ||
|
||
size_t out_offset = 0; | ||
for (size_t i = 0; i < n; ++i) { | ||
Tensor in_t = | ||
in->Slice(static_cast<int>(lod[0][i] + offset_data[i]), | ||
static_cast<int>(lod[0][i] + offset_data[i] + | ||
length_data[i])); | ||
|
||
StridedMemcpy<T>(ctx.device_context(), in_t.data<T>(), | ||
in_stride, in_t.dims(), out_stride, | ||
out->data<T>() + out_offset); | ||
out_offset += length_data[i] * in_stride[0]; | ||
} | ||
} | ||
}; | ||
|
||
template <typename Place, typename T> | ||
class SequenceSliceGradOpKernel : public framework::OpKernel<T> { | ||
public: | ||
void Compute(const framework::ExecutionContext& ctx) const override { | ||
auto* in = ctx.Input<LoDTensor>("X"); | ||
auto* offset = ctx.Input<Tensor>("Offset"); | ||
auto* length = ctx.Input<Tensor>("Length"); | ||
auto* out_grad = | ||
ctx.Input<framework::LoDTensor>(framework::GradVarName("Out")); | ||
auto* x_grad = | ||
ctx.Output<framework::LoDTensor>(framework::GradVarName("X")); | ||
|
||
const int64_t* offset_data = offset->data<int64_t>(); | ||
const int64_t* length_data = length->data<int64_t>(); | ||
|
||
if (platform::is_gpu_place(ctx.GetPlace())) { | ||
framework::Tensor offset_cpu; | ||
offset_cpu.mutable_data<T>(offset->dims(), platform::CPUPlace()); | ||
offset_cpu.CopyFrom(*offset, platform::CPUPlace(), ctx.device_context()); | ||
offset_data = offset_cpu.data<int64_t>(); | ||
|
||
framework::Tensor length_cpu; | ||
length_cpu.mutable_data<T>(length->dims(), platform::CPUPlace()); | ||
length_cpu.CopyFrom(*length, platform::CPUPlace(), ctx.device_context()); | ||
length_data = length_cpu.data<int64_t>(); | ||
} | ||
|
||
auto lod = in->lod(); | ||
auto out_lod = out_grad->lod(); | ||
|
||
x_grad->mutable_data<T>(ctx.GetPlace()); | ||
math::SetConstant<Place, T> set_zero; | ||
set_zero(ctx.device_context(), x_grad, static_cast<T>(0)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
auto out_grad_stride = framework::stride(out_grad->dims()); | ||
|
||
for (size_t i = 0; i < out_lod[0].size() - 1; ++i) { | ||
Tensor out_grad_t = | ||
out_grad->Slice(static_cast<int>(out_lod[0][i]), | ||
static_cast<int>(out_lod[0][i + 1])); | ||
auto out_grad_stride = framework::stride(out_grad_t.dims()); | ||
|
||
auto x_grad_stride = framework::stride(x_grad->dims()); | ||
|
||
Tensor x_grad_t = x_grad->Slice( | ||
static_cast<int>(lod[0][i] + offset_data[i]), | ||
static_cast<int>(lod[0][i] + offset_data[i] + length_data[i])); | ||
|
||
StridedMemcpy<T>(ctx.device_context(), out_grad_t.data<T>(), | ||
out_grad_stride, out_grad_t.dims(), x_grad_stride, | ||
x_grad_t.data<T>()); | ||
} | ||
} | ||
}; | ||
|
||
} // namespace operators | ||
} // namespace paddle |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import unittest | ||
import numpy as np | ||
import sys | ||
from op_test import OpTest | ||
|
||
class TestSequenceSliceOp(OpTest): | ||
def set_data(self): | ||
self.init_test_case() | ||
# only supprot one level LoD | ||
x = np.random.random(self.x_dim).astype('float32') | ||
lod = self.x_lod | ||
offset = np.array(self.offset).flatten().astype("int64") | ||
length = np.array(self.length).flatten().astype("int64") | ||
|
||
self.inputs = {'X': (x, lod), 'Offset': offset, 'Length': length} | ||
outs = [] #np.zeros((100, 3, 2)).astype('float32') | ||
out_lod = [[0]] | ||
out_lod_offset = 0 | ||
for i in range(len(offset)): | ||
sub_x = x[lod[0][i] + offset[i]: lod[0] | ||
[i] + offset[i] + length[i], :] | ||
out_lod_offset = out_lod_offset + len(sub_x) | ||
outs.append(sub_x) | ||
out_lod[0].append(out_lod_offset) | ||
outs = np.concatenate(outs, axis=0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is better that putting those code(line 19~25) into a function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those codes are short and only use once. |
||
self.outputs = {'Out': (outs, out_lod)} | ||
|
||
def init_test_case(self): | ||
self.x_dim = (100, 3, 2) | ||
self.x_lod = [[0, 20, 40, 60, 80, 100]] | ||
self.offset = [1, 2, 3, 4, 5] | ||
self.length = [10, 8, 6, 4, 2] | ||
|
||
def setUp(self): | ||
self.op_type = "sequence_slice" | ||
self.set_data() | ||
|
||
def test_check_output(self): | ||
self.check_output() | ||
|
||
def test_check_grad(self): | ||
self.check_grad(['X'], 'Out') | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you set
input_dims
toOut
? In some case,out_dim
is not equal toinput_dims
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function InferShape is called at the compiling stage, the offset and length are unknown, so I set the dims to the Maximum, and change them to the real-value at runtime.