Skip to content

Commit 484cff6

Browse files
authored
Merge pull request #9204 from pkuyym/fix-9171
Enhance LoDResetOp and add python wrapper
2 parents 0821ee7 + cd11b1b commit 484cff6

File tree

6 files changed

+242
-55
lines changed

6 files changed

+242
-55
lines changed

paddle/fluid/operators/lod_reset_op.cc

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,16 @@ class LoDResetOp : public framework::OperatorWithKernel {
2222
using framework::OperatorWithKernel::OperatorWithKernel;
2323

2424
void InferShape(framework::InferShapeContext *ctx) const override {
25-
// input check
2625
PADDLE_ENFORCE(ctx->HasInput("X"),
2726
"Input(X) of LoDResetOp should not be null.");
2827
PADDLE_ENFORCE(ctx->HasOutput("Out"),
2928
"Output(Out) of LoDResetOp should not be null.");
30-
// If target LoD is not set form Input(), then it must be set from Attr().
31-
if (!ctx->HasInput("TargetLoD")) {
29+
30+
if (!ctx->HasInput("Y")) {
3231
auto level0 = ctx->Attrs().Get<std::vector<int>>("target_lod");
33-
PADDLE_ENFORCE(level0.size() > 1,
34-
"Target LoD is not found, should be set to be a valid one "
35-
"through Input() or Attr().");
32+
PADDLE_ENFORCE_GT(level0.size(), 1,
33+
"If Input(Y) not provided, the target lod should be "
34+
"specified by attribute `target_lod`.");
3635
}
3736
ctx->SetOutputDim("Out", ctx->GetInputDim("X"));
3837
}
@@ -50,36 +49,77 @@ class LoDResetOpMaker : public framework::OpProtoAndCheckerMaker {
5049
public:
5150
LoDResetOpMaker(OpProto *proto, OpAttrChecker *op_checker)
5251
: OpProtoAndCheckerMaker(proto, op_checker) {
53-
AddInput("X", "(LoDTensor) The input tensor of lod_reset operator.");
54-
AddInput("TargetLoD",
55-
"(Tensor, optional) The target level 0 LoD from Input().")
52+
AddInput("X",
53+
"(Tensor, LoDTensor) Input variable of LoDResetOp which "
54+
"could be a Tensor or LoDTensor, where the data of output "
55+
"variable inherits from.");
56+
AddInput("Y",
57+
"(Tensor, LoDTensor, optional) If provided and Y is LoDTensor, "
58+
"lod of Input(Y) would be considered as the target lod first, "
59+
"otherwise data of Input(Y) would be considered as the "
60+
"target lod.")
5661
.AsDispensable();
57-
AddOutput("Out", "(LoDTensor) The output tensor of lod_reset operator.");
62+
AddOutput("Out",
63+
"(LoDTensor) Output variable of LoDResetOp which should be a "
64+
"LoDTensor.");
5865
AddAttr<std::vector<int>>("target_lod",
5966
"The target level 0 LoD from Attr().")
6067
.SetDefault(std::vector<int>{});
6168
AddComment(R"DOC(LoDReset operator
6269
63-
Reset LoD of Input(X) into a new one specified by Input(TargetLoD) or
64-
Attr(target_lod), or set LoD for Input(X) if it doesn't have one.
65-
Currently the lod_reset operator only supports the reset of level 0 LoD.
66-
At least one of Input(TargetLoD) and Attr(target_lod) must be set,
67-
and if both of them are set, Input(TargetLoD) will be chosen as the
68-
target LoD.
70+
Set LoD of `X` to a new one specified by `Y` or attribute `target_lod`. When `Y`
71+
provided and `Y` is a LoDTensor, `Y.lod` would be considered as target LoD
72+
first, otherwise `Y.data` would be considered as target LoD. If `Y` is not
73+
provided, target LoD should be specified by attribute `target_lod`.
74+
If target LoD is specified by `Y.data` or `target_lod`, only one level LoD
75+
is supported.
76+
77+
Example 1:
78+
79+
Given a 1-level LoDTensor input(X):
80+
X.lod = [[ 0, 2, 5 6 ]]
81+
X.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
82+
X.dims = [6, 1]
83+
84+
attr(target_lod): [0, 4, 6]
85+
86+
then we get a 1-level LoDTensor:
87+
Out.lod = [[ 0, 4, 6 ]]
88+
Out.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
89+
Out.dims = [6, 1]
90+
91+
Example 2:
6992
70-
An example:
71-
Given a float LoDTensor X with shape (6, 1), its transpose form represents
93+
Given a 1-level LoDTensor input(X):
94+
X.lod = [[ 0, 2, 5 6 ]]
95+
X.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
96+
X.dims = [6, 1]
7297
73-
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
98+
input(Y) is a Tensor:
99+
Y.data = [[0, 2, 6]]
100+
Y.dims = [1, 3]
74101
75-
with LoD = [[0, 2, 5, 6]] and the three (transposed) sequences look like
102+
then we get a 1-level LoDTensor:
103+
Out.lod = [[ 0, 2, 6 ]]
104+
Out.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
105+
Out.dims = [6, 1]
76106
77-
[1.0, 2.0], [3.0, 4.0, 5.0], [6.0].
107+
Example 3:
78108
79-
If target LoD = [0, 4, 6], the lod_reset operator will reset the LoD and
80-
the sequences that the LoDTensor Output(Out) contains becomes:
109+
Given a 1-level LoDTensor input(X):
110+
X.lod = [[ 0, 2, 5 6 ]]
111+
X.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
112+
X.dims = [6, 1]
81113
82-
[1.0, 2.0, 3.0, 4.0], [5.0, 6.0].
114+
input(Y) is a 2-level LoDTensor:
115+
Y.lod = [[0, 2, 4], [0, 2, 5, 6]]
116+
Y.data = [[1.1], [2.1], [3.1], [4.1], [5.1], [6.1]]
117+
Y.dims = [6, 1]
118+
119+
then we get a 2-level LoDTensor:
120+
Out.lod = [[0, 2, 4], [0, 2, 5, 6]]
121+
Out.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
122+
Out.dims = [6, 1]
83123
84124
)DOC");
85125
}
@@ -90,10 +130,16 @@ class LoDResetGradOp : public framework::OperatorWithKernel {
90130
using framework::OperatorWithKernel::OperatorWithKernel;
91131

92132
void InferShape(framework::InferShapeContext *ctx) const override {
93-
PADDLE_ENFORCE(ctx->HasInput("X"), "Input(X) shouldn't be null.");
133+
PADDLE_ENFORCE(ctx->HasInput("X"),
134+
"Input(X) of LoDResetGradOp should not be null.");
94135
PADDLE_ENFORCE(ctx->HasInput(framework::GradVarName("Out")),
95-
"Input(Out@GRAD) shouldn't be null.");
96-
ctx->SetOutputDim(framework::GradVarName("X"), ctx->GetInputDim("X"));
136+
"Input(Out@Grad) of LoDResetGradOp should not be null.");
137+
138+
auto x_grad_name = framework::GradVarName("X");
139+
if (ctx->HasOutput(x_grad_name)) {
140+
ctx->SetOutputDim(x_grad_name, ctx->GetInputDim("X"));
141+
ctx->ShareLoD("X", /*->*/ x_grad_name);
142+
}
97143
}
98144

99145
protected:
@@ -111,9 +157,13 @@ class LoDResetGradOp : public framework::OperatorWithKernel {
111157
namespace ops = paddle::operators;
112158
REGISTER_OP(lod_reset, ops::LoDResetOp, ops::LoDResetOpMaker, lod_reset_grad,
113159
ops::LoDResetGradOp);
114-
REGISTER_OP_CPU_KERNEL(lod_reset,
115-
ops::LoDResetKernel<paddle::platform::CPUPlace, float>,
116-
ops::LoDResetKernel<paddle::platform::CPUPlace, double>);
160+
REGISTER_OP_CPU_KERNEL(
161+
lod_reset, ops::LoDResetKernel<paddle::platform::CPUPlace, float>,
162+
ops::LoDResetKernel<paddle::platform::CPUPlace, double>,
163+
ops::LoDResetKernel<paddle::platform::CPUPlace, int>,
164+
ops::LoDResetKernel<paddle::platform::CPUPlace, int64_t>);
117165
REGISTER_OP_CPU_KERNEL(
118166
lod_reset_grad, ops::LoDResetGradKernel<paddle::platform::CPUPlace, float>,
119-
ops::LoDResetGradKernel<paddle::platform::CPUPlace, double>);
167+
ops::LoDResetGradKernel<paddle::platform::CPUPlace, double>,
168+
ops::LoDResetGradKernel<paddle::platform::CPUPlace, int>,
169+
ops::LoDResetGradKernel<paddle::platform::CPUPlace, int64_t>);

paddle/fluid/operators/lod_reset_op.cu

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ namespace ops = paddle::operators;
1818

1919
REGISTER_OP_CUDA_KERNEL(
2020
lod_reset, ops::LoDResetKernel<paddle::platform::CUDADeviceContext, float>,
21-
ops::LoDResetKernel<paddle::platform::CUDADeviceContext, double>);
21+
ops::LoDResetKernel<paddle::platform::CUDADeviceContext, double>,
22+
ops::LoDResetKernel<paddle::platform::CUDADeviceContext, int>,
23+
ops::LoDResetKernel<paddle::platform::CUDADeviceContext, int64_t>);
2224
REGISTER_OP_CUDA_KERNEL(
2325
lod_reset_grad,
2426
ops::LoDResetGradKernel<paddle::platform::CUDADeviceContext, float>,
25-
ops::LoDResetGradKernel<paddle::platform::CUDADeviceContext, double>);
27+
ops::LoDResetGradKernel<paddle::platform::CUDADeviceContext, double>,
28+
ops::LoDResetGradKernel<paddle::platform::CUDADeviceContext, int>,
29+
ops::LoDResetGradKernel<paddle::platform::CUDADeviceContext, int64_t>);

paddle/fluid/operators/lod_reset_op.h

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,46 @@ class LoDResetKernel : public framework::OpKernel<T> {
2626
void Compute(const framework::ExecutionContext& ctx) const {
2727
auto* out = ctx.Output<framework::LoDTensor>("Out");
2828
auto* in = ctx.Input<framework::LoDTensor>("X");
29-
auto* lod_t = ctx.Input<framework::Tensor>("TargetLoD");
29+
auto* lod_t = ctx.Input<framework::LoDTensor>("Y");
30+
31+
out->ShareDataWith(*in);
3032

3133
std::vector<int> level0;
3234
if (lod_t) {
33-
auto* lod = lod_t->data<int>();
34-
if (platform::is_gpu_place(ctx.GetPlace())) {
35-
framework::Tensor lod_cpu;
36-
framework::TensorCopy(*lod_t, platform::CPUPlace(),
37-
ctx.device_context(), &lod_cpu);
38-
lod = lod_cpu.data<int>();
35+
if (lod_t->lod().size() > 0) {
36+
auto y_lod = lod_t->lod();
37+
auto last_level = y_lod[y_lod.size() - 1];
38+
PADDLE_ENFORCE_EQ(last_level.back(), in->dims()[0],
39+
"Last value of `Y`'s last level LoD should be equal "
40+
"to the first dimension of `X`");
41+
out->set_lod(y_lod);
42+
return; // early return, since lod already set
43+
} else {
44+
auto* lod = lod_t->data<int>();
45+
if (platform::is_gpu_place(ctx.GetPlace())) {
46+
framework::Tensor lod_cpu;
47+
framework::TensorCopy(*lod_t, platform::CPUPlace(),
48+
ctx.device_context(), &lod_cpu);
49+
lod = lod_cpu.data<int>();
50+
}
51+
level0 = std::vector<int>(lod, lod + lod_t->numel());
3952
}
40-
level0 = std::vector<int>(lod, lod + lod_t->numel());
4153
} else {
4254
level0 = ctx.Attr<std::vector<int>>("target_lod");
4355
}
4456

45-
PADDLE_ENFORCE(level0.size() > 1UL,
46-
"The size of target LoD should be greater than 1.");
47-
PADDLE_ENFORCE(level0[0] == 0,
48-
"Target LoD should be a vector starting from 0.");
49-
PADDLE_ENFORCE(level0.back() == in->dims()[0],
50-
"Target LoD should be a vector end with the "
51-
"first dimension of Input(X).");
57+
PADDLE_ENFORCE_GT(level0.size(), 1UL,
58+
"Size of target LoD should be greater than 1.");
59+
PADDLE_ENFORCE_EQ(level0[0], 0,
60+
"Target LoD should be a vector starting from 0.");
61+
PADDLE_ENFORCE_EQ(level0.back(), in->dims()[0],
62+
"Target LoD should be a vector end with the "
63+
"first dimension of Input(X).");
5264
for (size_t i = 0; i < level0.size() - 1; ++i) {
5365
PADDLE_ENFORCE(level0[i + 1] > level0[i],
5466
"Target LoD should be an ascending vector.");
5567
}
5668

57-
out->ShareDataWith(*in);
5869
// cast level0 to size_t
5970
std::vector<size_t> ulevel0(level0.size(), 0);
6071
std::transform(level0.begin(), level0.end(), ulevel0.begin(),

python/paddle/fluid/layers/nn.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
'smooth_l1',
7474
'one_hot',
7575
'autoincreased_step_counter',
76+
'lod_reset',
7677
]
7778

7879

@@ -2225,7 +2226,7 @@ def reduce_prod(input, dim=None, keep_dim=False, name=None):
22252226
keep_dim (bool|False): Whether to reserve the reduced dimension in the
22262227
output Tensor. The result tensor will have one fewer dimension
22272228
than the :attr:`input` unless :attr:`keep_dim` is true.
2228-
name(str|None): A name for this layer(optional). If set None, the
2229+
name(str|None): A name for this layer(optional). If set None, the
22292230
layer will be named automatically.
22302231
22312232
Returns:
@@ -2241,7 +2242,7 @@ def reduce_prod(input, dim=None, keep_dim=False, name=None):
22412242
fluid.layers.reduce_prod(x) # [0.0002268]
22422243
fluid.layers.reduce_prod(x, dim=0) # [0.02, 0.06, 0.3, 0.63]
22432244
fluid.layers.reduce_prod(x, dim=-1) # [0.027, 0.0084]
2244-
fluid.layers.reduce_prod(x, dim=1,
2245+
fluid.layers.reduce_prod(x, dim=1,
22452246
keep_dim=True) # [[0.027], [0.0084]]
22462247
"""
22472248
helper = LayerHelper('reduce_prod', **locals())
@@ -3292,3 +3293,98 @@ def autoincreased_step_counter(counter_name=None, begin=1, step=1):
32923293
counter.stop_gradient = True
32933294

32943295
return counter
3296+
3297+
3298+
def lod_reset(x, y=None, target_lod=None):
3299+
"""
3300+
LoD Reset Operator. Set LoD of **x** to a new one specified by **y** or
3301+
**target_lod**. When **y** provided, **y.lod** would be considered as target
3302+
LoD first, otherwise **y.data** would be considered as target LoD. If **y**
3303+
is not provided, target LoD should be specified by **target_lod**.
3304+
If target LoD is specified by **Y.data** or **target_lod**, only one level
3305+
LoD is supported.
3306+
3307+
.. code-block:: text
3308+
3309+
* Example 1:
3310+
3311+
Given a 1-level LoDTensor x:
3312+
x.lod = [[ 0, 2, 5 6 ]]
3313+
x.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
3314+
x.dims = [6, 1]
3315+
3316+
target_lod: [0, 4, 6]
3317+
3318+
then we get a 1-level LoDTensor:
3319+
out.lod = [[ 0, 4, 6 ]]
3320+
out.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
3321+
out.dims = [6, 1]
3322+
3323+
* Example 2:
3324+
3325+
Given a 1-level LoDTensor x:
3326+
x.lod = [[ 0, 2, 5 6 ]]
3327+
x.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
3328+
x.dims = [6, 1]
3329+
3330+
y is a Tensor:
3331+
y.data = [[0, 2, 6]]
3332+
y.dims = [1, 3]
3333+
3334+
then we get a 1-level LoDTensor:
3335+
out.lod = [[ 0, 2, 6 ]]
3336+
out.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
3337+
out.dims = [6, 1]
3338+
3339+
* Example 3:
3340+
3341+
Given a 1-level LoDTensor x:
3342+
x.lod = [[ 0, 2, 5 6 ]]
3343+
x.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
3344+
x.dims = [6, 1]
3345+
3346+
y is a 2-level LoDTensor:
3347+
y.lod = [[0, 2, 4], [0, 2, 5, 6]]
3348+
y.data = [[1.1], [2.1], [3.1], [4.1], [5.1], [6.1]]
3349+
y.dims = [6, 1]
3350+
3351+
then we get a 2-level LoDTensor:
3352+
out.lod = [[0, 2, 4], [0, 2, 5, 6]]
3353+
out.data = [[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]
3354+
out.dims = [6, 1]
3355+
3356+
Args:
3357+
x (Variable): Input variable which could be a Tensor or LodTensor.
3358+
y (Variable|None): If provided, output's LoD would be derived from y.
3359+
target_lod (list|tuple|None): One level LoD which should be considered
3360+
as target LoD when y not provided.
3361+
3362+
Returns:
3363+
Variable: Output variable with LoD specified by this operator.
3364+
3365+
Raises:
3366+
ValueError: If y and target_lod are both None.
3367+
3368+
Examples:
3369+
.. code-block:: python
3370+
3371+
x = layers.data(name='x', shape=[10])
3372+
y = layers.data(name='y', shape=[10, 20], lod_level=2)
3373+
out = layers.lod_reset(x=x, y=y)
3374+
"""
3375+
helper = LayerHelper("lod_reset", **locals())
3376+
out = helper.create_tmp_variable(dtype=x.dtype)
3377+
if y is not None:
3378+
helper.append_op(
3379+
type="lod_reset", inputs={'X': x,
3380+
'Y': y}, outputs={'Out': out})
3381+
elif target_lod is not None:
3382+
helper.append_op(
3383+
type="lod_reset",
3384+
inputs={'X': x},
3385+
attrs={'target_lod': target_lod},
3386+
outputs={'Out': out})
3387+
else:
3388+
raise ValueError("y and target_lod should not be both None.")
3389+
3390+
return out

python/paddle/fluid/tests/unittests/test_layers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@ def test_smooth_l1(self):
327327
self.assertIsNotNone(loss)
328328
print(str(program))
329329

330+
def test_lod_reset(self):
331+
program = Program()
332+
with program_guard(program):
333+
x = layers.data(name='x', shape=[10], dtype='float32')
334+
y = layers.data(
335+
name='y', shape=[10, 20], dtype='float32', lod_level=2)
336+
print(layers.lod_reset(x=x, y=y))
337+
print(str(program))
338+
330339

331340
if __name__ == '__main__':
332341
unittest.main()

0 commit comments

Comments
 (0)