Files
paddlepaddle--paddle/paddle/phi/kernels/impl/matmul_grad_kernel_impl.h
T
2026-07-13 12:40:42 +08:00

2387 lines
81 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
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 "glog/logging.h"
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/kernels/complex_kernel.h"
#include "paddle/phi/kernels/empty_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/funcs/reduce_function.h"
#include "paddle/phi/kernels/funcs/reduce_functor.h"
#include "paddle/phi/kernels/impl/dot_grad_kernel_impl.h"
#include "paddle/phi/kernels/impl/matmul_kernel_impl.h"
#include "paddle/phi/kernels/reduce_sum_kernel.h"
#include "paddle/phi/kernels/scale_kernel.h"
#if defined(__NVCC__) || defined(__HIPCC__)
#include "paddle/phi/kernels/gpu/reduce.h"
#endif
COMMON_DECLARE_bool(use_legacy_gemm);
namespace phi {
template <typename Context, typename T>
struct ReduceSumForMatmulGrad {
void operator()(const Context& dev_ctx,
const DenseTensor& input,
DenseTensor* output,
const std::vector<int>& reduce_dims);
};
template <typename T>
struct ReduceSumForMatmulGrad<CPUContext, T> {
void operator()(const CPUContext& dev_ctx,
const DenseTensor& input,
DenseTensor* output,
const std::vector<int>& reduce_dims) {
std::vector<int64_t> reduce_dims_tmp(reduce_dims.begin(),
reduce_dims.end());
funcs::ReduceKernelImpl<CPUContext, T, T, funcs::SumFunctor>(
dev_ctx, input, output, reduce_dims_tmp, true, false);
}
};
#if defined(__NVCC__) || defined(__HIPCC__)
template <typename T>
struct ReduceSumForMatmulGrad<GPUContext, T> {
void operator()(const GPUContext& dev_ctx,
const DenseTensor& input,
DenseTensor* output,
const std::vector<int>& reduce_dims) {
SumKernel<T, GPUContext>(
dev_ctx, input, reduce_dims, input.dtype(), false, output);
}
};
#endif
// Reshape a rank-3 tensor from P x M x N to (P * M) x N.
// Identity op if the tensor is not of rank 3.
static DenseTensor FoldInitDims(const DenseTensor& input) {
DenseTensor output = input;
auto in_dims = input.dims();
if (in_dims.size() == 3) {
output.Resize({in_dims[0] * in_dims[1], in_dims[2]});
}
return output;
}
// Reshape a rank-3 tensor from P x M x N to M x (P * N).
// (Warning: This requires transposing data and writes into new memory.)
// Identity op if the tensor is not of rank 3.
template <typename Context, typename T>
static DenseTensor FoldHeadAndLastDims(const Context& dev_ctx,
const DenseTensor& input) {
auto in_dims = input.dims();
if (in_dims.size() != 3) {
return input;
}
DenseTensor output = EmptyLike<T, Context>(dev_ctx, input);
output.Resize({in_dims[1], in_dims[0], in_dims[2]});
std::vector<int> axis = {1, 0, 2};
funcs::Transpose<Context, T, 3> trans;
trans(dev_ctx, input, &output, axis);
output.Resize({in_dims[1], in_dims[0] * in_dims[2]});
return output;
}
#if defined(PADDLE_WITH_CUDA) && !defined(PADDLE_WITH_HIP) && !defined(_WIN32)
// Reshape a rank-3 tensor from B x M x N to (B * N) x M.
// In order to perform [M, BN] x [BN, K] -> [M, K] to save reduce cost
// Avoiding [1,0,2] permute for better performance
// (Warning: This requires transposing data and writes into new memory.)
// Identity op if the tensor is not of rank 3.
template <typename Context, typename T>
static DenseTensor FoldBatchIntoAggregation(const Context& dev_ctx,
const DenseTensor& input) {
auto in_dims = input.dims();
if (in_dims.size() != 3) {
return input;
}
DenseTensor output = TransposeLast2Dim<T>(dev_ctx, input);
output.Resize({in_dims[0] * in_dims[2], in_dims[1]});
return output;
}
#endif
template <typename Context, typename T>
typename std::enable_if<!std::is_integral<T>::value>::type MatMul(
const Context& dev_ctx,
const DenseTensor& a,
bool trans_a,
const DenseTensor& b,
bool trans_b,
DenseTensor* out,
bool flag = false) {
dev_ctx.template Alloc<T>(out);
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
auto mat_dim_a = funcs::CreateMatrixDescriptor(a.dims(), 0, trans_a);
auto mat_dim_b = funcs::CreateMatrixDescriptor(b.dims(), 0, trans_b);
if (a.dims().size() == 3 && b.dims().size() <= 2) {
// the transpose_X must be false, if is true, the transpose cost much time
if (!trans_a) {
mat_dim_a.height_ *= mat_dim_a.batch_size_;
mat_dim_a.batch_size_ = 0;
}
}
blas.MatMul(a.data<T>(),
mat_dim_a,
b.data<T>(),
mat_dim_b,
static_cast<T>(1),
dev_ctx.template Alloc<T>(out),
static_cast<T>(flag));
}
/**
* Get row matrix shape from a vector shape. If the rank of x_dim > 1, the
* original x_dim is returned.
*/
static DDim RowMatrixFromVector(const DDim& x_dim) {
if (x_dim.size() > 1) {
return x_dim;
}
return make_ddim({1, x_dim[0]});
}
/**
* Get column matrix shape from a vector shape. If the ran of y_dim > 1, the
* original y_dim is returned.
*/
static DDim ColumnMatrixFromVector(const DDim& y_dim) {
if (y_dim.size() > 1) {
return y_dim;
}
return make_ddim({y_dim[0], 1});
}
/**
* Reshape a tensor to 3-D or 2-D tensor by matrix descriptor.
*
* The shape would be [BatchSize, H, W] or [H, W].
* If transposed, `H,W` will be swapped.
*/
static void ReshapeTensorIntoMatrixSequence(
DenseTensor* x, const funcs::MatDescriptor& descriptor) {
int64_t h, w;
h = descriptor.height_;
w = descriptor.width_;
if (descriptor.trans_) {
std::swap(w, h);
}
if (descriptor.batch_size_) {
x->Resize({descriptor.batch_size_, h, w});
} else {
x->Resize({h, w});
}
}
static void ReshapeXYOutIntoMatrixSequence(DenseTensor* x,
DenseTensor* y,
DenseTensor* out,
bool trans_x,
bool trans_y) {
auto x_dim = RowMatrixFromVector(x->dims());
auto y_dim = ColumnMatrixFromVector(y->dims());
auto mat_dim_x = funcs::CreateMatrixDescriptor(x_dim, 0, trans_x);
auto mat_dim_y = funcs::CreateMatrixDescriptor(y_dim, 0, trans_y);
if (mat_dim_x.batch_size_ == 0 && mat_dim_y.batch_size_ == 0) {
out->Resize({mat_dim_x.height_, mat_dim_y.width_});
} else {
out->Resize({(std::max)(mat_dim_x.batch_size_, mat_dim_y.batch_size_),
mat_dim_x.height_,
mat_dim_y.width_});
}
ReshapeTensorIntoMatrixSequence(x, mat_dim_x);
ReshapeTensorIntoMatrixSequence(y, mat_dim_y);
}
template <typename T, typename Context>
void CalcInputGrad(const Context& dev_ctx,
const DenseTensor& a,
bool trans_a,
bool is_fold_init_dims_a,
const DenseTensor& b,
bool trans_b,
bool is_fold_init_dims_b,
DenseTensor* out,
bool flag = false,
bool using_optimized_gemm = false) {
// disabling optimized gemm for high-level derivative calculation, for better
// precision.
if (out == nullptr) return;
bool need_combine =
(a.dims().size() == 3 || b.dims().size() == 3) && out->dims().size() == 2;
#if defined(PADDLE_WITH_CUDA) && !defined(PADDLE_WITH_HIP) && !defined(_WIN32)
if (!FLAGS_use_legacy_gemm && using_optimized_gemm) {
DenseTensor a_processed = a, b_processed = b;
bool trans_a_processed = trans_a, trans_b_processed = trans_b;
if (need_combine) {
a_processed = is_fold_init_dims_a
? FoldInitDims(a)
: FoldBatchIntoAggregation<Context, T>(dev_ctx, a);
b_processed = is_fold_init_dims_b
? FoldInitDims(b)
: FoldBatchIntoAggregation<Context, T>(dev_ctx, b);
// Once we try to combine aggregation dimension to batch dimension,
// we need to flip the transpose flag
trans_a_processed = is_fold_init_dims_a ? trans_a : !trans_a;
trans_b_processed = is_fold_init_dims_b ? trans_b : !trans_b;
} // if need_combine and in new gemm dispatch logic.
std::vector<std::int64_t> a_dims = vectorize(a_processed.dims());
std::vector<std::int64_t> b_dims = vectorize(b_processed.dims());
MatMulFunction<Context, T>(dev_ctx,
a_processed,
b_processed,
a_dims,
b_dims,
out,
trans_a_processed,
trans_b_processed);
} else // NOLINT
#endif // LINUX && CUDA GPU only
{ // NOLINT
// legacy no-broadcast matmul dispatch logic, using high-dim permute,
// which is suffer from low-performance, and using less optimized
// matmul-api.
if (!need_combine) {
MatMul<Context, T>(dev_ctx, a, trans_a, b, trans_b, out, flag);
} else {
DenseTensor a_processed =
is_fold_init_dims_a ? FoldInitDims(a)
: FoldHeadAndLastDims<Context, T>(dev_ctx, a);
DenseTensor b_processed =
is_fold_init_dims_b ? FoldInitDims(b)
: FoldHeadAndLastDims<Context, T>(dev_ctx, b);
MatMul<Context, T>(
dev_ctx, a_processed, trans_a, b_processed, trans_b, out, flag);
} // if need_combine and in legacy gemm dispatch logic
} // legacy matmul dispatch logic
}
template <typename T, typename Context>
void MatmulGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const DenseTensor& out_grad,
bool transpose_x,
bool transpose_y,
DenseTensor* dx,
DenseTensor* dy) {
if (x.numel() == 0) {
dev_ctx.template Alloc<T>(dx);
Full<T, Context>(dev_ctx, y.dims(), 0, dy);
return;
}
if (y.numel() == 0) {
dev_ctx.template Alloc<T>(dy);
Full<T, Context>(dev_ctx, x.dims(), 0, dx);
return;
}
if (!transpose_x && transpose_y && y.dims().size() < 2) {
transpose_y = false;
}
// get dims
std::vector<std::int64_t> x_dims = vectorize(x.dims());
std::vector<std::int64_t> y_dims = vectorize(y.dims());
std::vector<std::int64_t> dout_dims = vectorize(out_grad.dims());
int x_ndim = x_dims.size();
int y_ndim = y_dims.size();
int ndim = dout_dims.size();
// Case1 : x's or y's dim = 1
if (x_ndim == 1 && y_ndim == 1) {
if (dx) dev_ctx.template Alloc<T>(dx);
if (dy) dev_ctx.template Alloc<T>(dy);
if (out_grad.numel() == 1) {
DotGradFunction<Context, T>()(dev_ctx, &x, &y, &out_grad, dx, dy);
return;
}
}
bool is_broadcast = true;
if (y_ndim <= 2 || x_ndim <= 2) {
is_broadcast = false;
} else if (x_ndim != y_ndim) {
is_broadcast = true;
} else {
is_broadcast = !std::equal(
x_dims.cbegin(), x_dims.cbegin() + x_ndim - 2, y_dims.cbegin());
}
bool is_y_been_broadcasted = false;
bool is_x_been_broadcasted = false;
// NOTE(Pan Zhaowu): Figure out which tensor is been broadcasted,
// to combine the broadcasted dim of other tensor into aggregation dim,
// avoiding use batched gemm and saving reduction cost.
if (is_broadcast) {
if (x_ndim != y_ndim) {
is_x_been_broadcasted = x_ndim < y_ndim;
is_y_been_broadcasted = !is_x_been_broadcasted;
} else {
int64_t x_batch = 1;
int64_t y_batch = 1;
for (int i = 0; i < x_ndim - 2; ++i) {
x_batch *= x_dims[i];
}
for (int i = 0; i < y_ndim - 2; ++i) {
y_batch *= y_dims[i];
}
is_x_been_broadcasted = x_batch < y_batch;
is_y_been_broadcasted = !is_x_been_broadcasted;
}
}
// for complex
DenseTensor x_conj;
DenseTensor y_conj;
// Case2: no broadcast or no batch size, it aims to speed and it is same as
// matmul in old version.
if (!is_broadcast) {
DenseTensor x_help = x;
DenseTensor y_help = y;
DenseTensor out_grad_help = out_grad;
ReshapeXYOutIntoMatrixSequence(
&x_help, &y_help, &out_grad_help, transpose_x, transpose_y);
DDim dx_dims;
if (dx) {
dx_dims = dx->dims();
if (dx_dims != x_help.dims()) {
dx->Resize(x_help.dims());
}
y_conj = Conj<T>(dev_ctx, y_help);
}
DDim dy_dims;
if (dy) {
dy_dims = dy->dims();
if (dy_dims != y_help.dims()) {
dy->Resize(y_help.dims());
}
x_conj = Conj<T>(dev_ctx, x_help);
}
if (transpose_x && transpose_y) {
#if defined(PADDLE_WITH_CUDA) && !defined(PADDLE_WITH_HIP) && !defined(_WIN32)
if (!FLAGS_use_legacy_gemm && x_help.dims().size() == 3 &&
y_help.dims().size() == 3) {
// For batched case only (both x and y are 3D after reshape): match
// PyTorch's backward cublas call pattern (OP_N/OP_N instead of
// OP_T/OP_T), compute without transposes and transpose the results.
// dX = (dOut @ Y_conj)^T, dY = (X_conj @ dOut)^T
if (dx) {
auto dx_dims_orig = dx->dims();
DenseTensor dx_tmp = EmptyLike<T, Context>(dev_ctx, *dx);
dx_tmp.Resize({dx_tmp.dims()[0], dx_tmp.dims()[2], dx_tmp.dims()[1]});
CalcInputGrad<T>(dev_ctx,
out_grad_help,
false,
true,
y_conj,
false,
false,
&dx_tmp,
false,
true);
dev_ctx.template Alloc<T>(dx);
std::vector<int> axis = {0, 2, 1};
funcs::Transpose<Context, T, 3> trans;
trans(dev_ctx, dx_tmp, dx, axis);
dx->Resize(dx_dims_orig);
}
if (dy) {
auto dy_dims_orig = dy->dims();
DenseTensor dy_tmp = EmptyLike<T, Context>(dev_ctx, *dy);
dy_tmp.Resize({dy_tmp.dims()[0], dy_tmp.dims()[2], dy_tmp.dims()[1]});
CalcInputGrad<T>(dev_ctx,
x_conj,
false,
true,
out_grad_help,
false,
false,
&dy_tmp,
false,
true);
dev_ctx.template Alloc<T>(dy);
std::vector<int> axis = {0, 2, 1};
funcs::Transpose<Context, T, 3> trans;
trans(dev_ctx, dy_tmp, dy, axis);
dy->Resize(dy_dims_orig);
}
} else // NOLINT
#endif
{ // NOLINT
CalcInputGrad<T>(dev_ctx,
y_conj,
true,
true,
out_grad_help,
true,
false,
dx,
false,
true);
CalcInputGrad<T>(dev_ctx,
out_grad_help,
true,
true,
x_conj,
true,
false,
dy,
false,
true);
}
} else if (transpose_x) {
CalcInputGrad<T>(dev_ctx,
y_conj,
false,
false,
out_grad_help,
true,
false,
dx,
false,
true);
CalcInputGrad<T>(dev_ctx,
x_conj,
false,
false,
out_grad_help,
false,
true,
dy,
false,
true);
} else if (transpose_y) {
CalcInputGrad<T>(dev_ctx,
out_grad_help,
false,
false,
y_conj,
false,
true,
dx,
false,
true);
#if defined(PADDLE_WITH_CUDA) && !defined(PADDLE_WITH_HIP) && !defined(_WIN32)
if (!FLAGS_use_legacy_gemm && x_help.dims().size() == 3 &&
y_help.dims().size() == 3) {
// For batched case only (both x and y are 3D after reshape): match
// PyTorch's backward cublas call pattern for dY. Compute X_conj^T @
// dOut into a temp buffer with transposed shape, then transpose the
// result. This produces the same cublas descriptor layout as PyTorch,
// ensuring identical algorithm selection and bitwise alignment.
if (dy) {
auto dy_dims_orig = dy->dims();
DenseTensor dy_tmp = EmptyLike<T, Context>(dev_ctx, *dy);
dy_tmp.Resize({dy_tmp.dims()[0], dy_tmp.dims()[2], dy_tmp.dims()[1]});
CalcInputGrad<T>(dev_ctx,
x_conj,
true,
true,
out_grad_help,
false,
false,
&dy_tmp,
false,
true);
dev_ctx.template Alloc<T>(dy);
std::vector<int> axis = {0, 2, 1};
funcs::Transpose<Context, T, 3> trans;
trans(dev_ctx, dy_tmp, dy, axis);
dy->Resize(dy_dims_orig);
}
} else // NOLINT
#endif
{ // NOLINT
CalcInputGrad<T>(dev_ctx,
out_grad_help,
true,
true,
x_conj,
false,
true,
dy,
false,
true);
}
} else {
CalcInputGrad<T>(dev_ctx,
out_grad_help,
false,
false,
y_conj,
true,
false,
dx,
false,
true);
CalcInputGrad<T>(dev_ctx,
x_conj,
true,
true,
out_grad_help,
false,
true,
dy,
false,
true);
}
if (dx) {
if (dx_dims != x_help.dims()) {
dx->Resize(dx_dims);
}
// Ensure output shape matches original input shape
if (x.dims().size() == 1 && dx->dims().size() == 2) {
if (dx->dims()[1] == 1) {
dx->Resize({dx->dims()[0]});
} else if (dx->dims()[0] == 1) {
dx->Resize({dx->dims()[1]});
}
}
}
if (dy) {
if (dy_dims != y_help.dims()) {
dy->Resize(dy_dims);
}
// Ensure output shape matches original input shape
if (y.dims().size() == 1 && dy->dims().size() == 2) {
if (dy->dims()[1] == 1) {
dy->Resize({dy->dims()[0]});
} else if (dy->dims()[0] == 1) {
dy->Resize({dy->dims()[1]});
}
}
}
} else {
// Case3: broadcast. It need cost much time to reduce sum for the
// broadcast and wastes the memory.
// So we should avoid the case in reality.
VLOG(3) << "It need cost much time to reduce sum for the broadcast and "
"wastes the memory. So we should avoid the case in reality";
x_conj = Conj<T>(dev_ctx, x);
y_conj = Conj<T>(dev_ctx, y);
DenseTensor dx_help;
DenseTensor dy_help;
if (transpose_x) {
if (transpose_y) {
// X'Y': dA = Y'G', dB = G'X'
if (dx)
MatMulFunction<Context, T>(dev_ctx,
y_conj,
out_grad,
y_dims,
dout_dims,
&dx_help,
true,
true);
if (dy)
MatMulFunction<Context, T>(dev_ctx,
out_grad,
x_conj,
dout_dims,
x_dims,
&dy_help,
true,
true);
} else {
// X'Y: dX = YG', dY = XG
if (dx)
MatMulFunction<Context, T>(dev_ctx,
y_conj,
out_grad,
y_dims,
dout_dims,
&dx_help,
false,
true);
if (dy)
MatMulFunction<Context, T>(dev_ctx,
x_conj,
out_grad,
x_dims,
dout_dims,
&dy_help,
false,
false);
}
} else {
if (transpose_y) {
// XY': dX = GY, dY = G'X
if (dx)
MatMulFunction<Context, T>(dev_ctx,
out_grad,
y_conj,
dout_dims,
y_dims,
&dx_help,
false,
false);
if (dy)
MatMulFunction<Context, T>(dev_ctx,
out_grad,
x_conj,
dout_dims,
x_dims,
&dy_help,
true,
false);
} else {
// XY: dX = GY', dY = X'G
VLOG(3)
<< "matmul grad case: transpose_x = false && transpose_y = false";
if (dx) {
#if defined(PADDLE_WITH_CUDA) && !defined(PADDLE_WITH_HIP) && !defined(_WIN32)
if (!FLAGS_use_legacy_gemm && is_x_been_broadcasted && x_ndim == 3 &&
ndim == 3) {
// Once x been broadcasted, we introduce a new aggregate dim
// original: [B, M, N] x [B, K, N]' -> [B, M, K] -(reduceB)-> [M, K]
// new: [BN, M] x [BN, K] -> [M, K]
DenseTensor out_grad_processed =
TransposeLast2Dim<T>(dev_ctx, out_grad);
DenseTensor y_conj_processed =
TransposeLast2Dim<T>(dev_ctx, y_conj);
int64_t BN = 1;
std::vector<std::int64_t> y_processed_dims =
vectorize(y_conj_processed.dims());
for (int i = 0; i < ndim - 1; i++) {
BN *= y_processed_dims[i];
}
std::vector<std::int64_t> out_grad_2d_dim{BN, dout_dims[ndim - 2]};
std::vector<std::int64_t> y_conj_2d_dim{BN, y_dims[y_ndim - 2]};
out_grad_processed.Resize(out_grad_2d_dim);
y_conj_processed.Resize(y_conj_2d_dim);
// 2D x 2D -> 2D
MatMulFunction<Context, T>(dev_ctx,
out_grad_processed,
y_conj_processed,
out_grad_2d_dim,
y_conj_2d_dim,
&dx_help,
true,
false);
// make legacy reduce logic happy
std::vector<std::int64_t> x_grad_dim(ndim);
for (int i = 0; i < ndim - 2; i++) {
x_grad_dim[i] = 1;
}
x_grad_dim[ndim - 2] = dx_help.dims()[0];
x_grad_dim[ndim - 1] = dx_help.dims()[1];
dx_help.Resize(x_grad_dim);
} else // NOLINT
#endif
{ // NOLINT
MatMulFunction<Context, T>(dev_ctx,
out_grad,
y_conj,
dout_dims,
y_dims,
&dx_help,
false,
true);
} // if is_x_been_broadcasted
} // if dx
if (dy) {
#if defined(PADDLE_WITH_CUDA) && !defined(PADDLE_WITH_HIP) && !defined(_WIN32)
if (!FLAGS_use_legacy_gemm && is_y_been_broadcasted && y_ndim == 3 &&
ndim == 3) {
// Once y been broadcasted, we introduce a new aggregate dim
// original: [B, M, K] x [B, M, N] -> [B, K, N] -(reduceB)-> [K, N]
// new: [BM, K]' x [BM, N] -> [K, N]
int64_t BM = 1;
for (int i = 0; i < ndim - 1; i++) {
BM *= x_dims[i];
}
std::vector<std::int64_t> out_grad_2d_dim{BM, dout_dims[ndim - 1]};
std::vector<std::int64_t> x_conj_2d_dim{BM, x_dims[x_ndim - 1]};
DenseTensor out_grad_processed = out_grad;
DenseTensor x_conj_processed = x_conj;
out_grad_processed.Resize(out_grad_2d_dim);
x_conj_processed.Resize(x_conj_2d_dim);
MatMulFunction<Context, T>(dev_ctx,
x_conj_processed,
out_grad_processed,
x_conj_2d_dim,
out_grad_2d_dim,
&dy_help,
true,
false);
// make legacy reduce logic happy
std::vector<std::int64_t> y_grad_dim(ndim);
for (int i = 0; i < ndim - 2; i++) {
y_grad_dim[i] = 1;
}
y_grad_dim[ndim - 2] = dy_help.dims()[0];
y_grad_dim[ndim - 1] = dy_help.dims()[1];
dy_help.Resize(y_grad_dim);
} else // NOLINT
#endif
{ // NOLINT
MatMulFunction<Context, T>(dev_ctx,
x_conj,
out_grad,
x_dims,
dout_dims,
&dy_help,
true,
false);
} // if is_y_been_broadcasted
} // if dy
}
}
// get help dims
const std::vector<std::int64_t> dx_help_dims = vectorize(dx_help.dims());
const std::vector<std::int64_t> dy_help_dims = vectorize(dy_help.dims());
std::vector<std::int64_t> dx_broadcast_dims(ndim);
std::vector<std::int64_t> dy_broadcast_dims(ndim);
std::fill(
dx_broadcast_dims.data(), dx_broadcast_dims.data() + ndim - x_ndim, 1);
std::fill(
dy_broadcast_dims.data(), dy_broadcast_dims.data() + ndim - y_ndim, 1);
std::copy(x_dims.data(),
x_dims.data() + x_ndim,
dx_broadcast_dims.data() + ndim - x_ndim);
std::copy(y_dims.data(),
y_dims.data() + y_ndim,
dy_broadcast_dims.data() + ndim - y_ndim);
std::vector<int> dx_reduce_dims;
std::vector<int> dy_reduce_dims;
for (int idx = 0; idx <= ndim - 3; idx++) {
if (dx_help_dims[idx] != 1 && dx_broadcast_dims[idx] == 1) {
dx_reduce_dims.push_back(idx);
}
if (dy_help_dims[idx] != 1 && dy_broadcast_dims[idx] == 1) {
dy_reduce_dims.push_back(idx);
}
}
// reduce sum to get grad by ReduceSum
if (dx) {
if (dx_reduce_dims.empty()) {
*dx = std::move(dx_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, dx_help, dx, dx_reduce_dims);
}
dx->Resize(x.dims());
// Ensure output shape matches original input shape
if (x.dims().size() == 1 && dx->dims().size() == 2) {
if (dx->dims()[1] == 1) {
dx->Resize({dx->dims()[0]});
} else if (dx->dims()[0] == 1) {
dx->Resize({dx->dims()[1]});
}
}
}
if (dy) {
if (dy_reduce_dims.empty()) {
*dy = std::move(dy_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, dy_help, dy, dy_reduce_dims);
}
dy->Resize(y.dims());
// Ensure output shape matches original input shape
if (y.dims().size() == 1 && dy->dims().size() == 2) {
if (dy->dims()[1] == 1) {
dy->Resize({dy->dims()[0]});
} else if (dy->dims()[0] == 1) {
dy->Resize({dy->dims()[1]});
}
}
}
// Get the OutputGrad(out)
}
}
template <typename T, typename Context>
void MatmulDoubleGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const DenseTensor& dout,
const optional<DenseTensor>& ddx,
const optional<DenseTensor>& ddy,
bool transpose_x,
bool transpose_y,
DenseTensor* dx,
DenseTensor* dy,
DenseTensor* ddout) {
// Get dims from the input x, y, output_grad
std::vector<std::int64_t> x_dims = vectorize(x.dims());
std::vector<std::int64_t> y_dims = vectorize(y.dims());
std::vector<std::int64_t> dout_dims = vectorize(dout.dims());
int x_ndim = x_dims.size();
int y_ndim = y_dims.size();
int ndim = dout_dims.size();
// Case1 : x's or y's dim = 1
if (x_ndim == 1 && y_ndim == 1) {
DotDoubleGradFunction<Context, T>()(
dev_ctx, &x, &y, &dout, &ddx, &ddy, dx, dy, ddout);
return;
}
DenseTensor x_conj;
DenseTensor y_conj;
DenseTensor dout_conj;
bool is_broadcast = true;
if (x_ndim <= 2 || y_ndim <= 2) {
is_broadcast = false;
} else if (x_ndim != y_ndim) {
is_broadcast = true;
} else {
is_broadcast = !std::equal(
x_dims.cbegin(), x_dims.cbegin() + x_ndim - 2, y_dims.cbegin());
}
if (!is_broadcast) {
// Case2: no broadcast or no batch size
DenseTensor x_help = x;
DenseTensor y_help = y;
DenseTensor dout_help = dout;
ReshapeXYOutIntoMatrixSequence(
&x_help, &y_help, &dout_help, transpose_x, transpose_y);
DDim dx_dims;
if (dx) {
dx_dims = dx->dims();
if (dx_dims != x_help.dims()) {
dx->Resize(x_help.dims());
}
}
DDim dy_dims;
if (dy) {
dy_dims = dy->dims();
if (dy_dims != y_help.dims()) {
dy->Resize(y_help.dims());
}
}
DDim ddout_dims;
if (ddout) {
ddout_dims = ddout->dims();
if (ddout_dims != dout_help.dims()) {
ddout->Resize(dout_help.dims());
}
x_conj = Conj<T>(dev_ctx, x_help);
y_conj = Conj<T>(dev_ctx, y_help);
}
if (dx || dy) {
dout_conj = Conj<T>(dev_ctx, dout_help);
}
bool ddout_flag = false;
if (ddx) {
auto ddx_mat = ddx.get();
if (ddx_mat.dims() != x_help.dims()) {
ddx_mat.Resize(x_help.dims());
}
if (dy) {
if (transpose_x && transpose_y) {
// dy = dout' * ddx'
CalcInputGrad<T>(
dev_ctx, dout_conj, true, true, ddx_mat, true, false, dy, false);
} else if (transpose_x) {
// dy = ddx * dout
CalcInputGrad<T>(dev_ctx,
ddx_mat,
false,
false,
dout_conj,
false,
true,
dy,
false);
} else if (transpose_y) {
// dy = dout' * ddx
CalcInputGrad<T>(
dev_ctx, dout_conj, true, true, ddx_mat, false, true, dy, false);
} else {
// dy = ddx' * dout
CalcInputGrad<T>(
dev_ctx, ddx_mat, true, true, dout_conj, false, true, dy, false);
}
}
if (ddout) {
CalcInputGrad<T>(dev_ctx,
ddx_mat,
transpose_x,
true,
y_conj,
transpose_y,
false,
ddout,
ddout_flag);
ddout_flag = true;
}
} else if (!ddx && dy) {
FullLikeKernel<T, Context>(dev_ctx, y, Scalar(0.0), y.dtype(), dy);
}
if (ddy) {
auto ddy_mat = ddy.get();
if (ddy_mat.dims() != y_help.dims()) {
ddy_mat.Resize(y_help.dims());
}
if (dx) {
if (transpose_x && transpose_y) {
// dx = ddy' * dout'
CalcInputGrad<T>(
dev_ctx, ddy_mat, true, true, dout_conj, true, false, dx, false);
} else if (transpose_x) {
// dx = ddy * dout'
CalcInputGrad<T>(dev_ctx,
ddy_mat,
false,
false,
dout_conj,
true,
false,
dx,
false);
} else if (transpose_y) {
// dx = dout * ddy
CalcInputGrad<T>(dev_ctx,
dout_conj,
false,
false,
ddy_mat,
false,
true,
dx,
false);
} else {
// dx = dout * ddy'
CalcInputGrad<T>(dev_ctx,
dout_conj,
false,
false,
ddy_mat,
true,
false,
dx,
false);
}
}
if (ddout) {
CalcInputGrad<T>(dev_ctx,
x_conj,
transpose_x,
true,
ddy_mat,
transpose_y,
false,
ddout,
ddout_flag);
}
} else if (!ddy && dx) {
FullLikeKernel<T, Context>(dev_ctx, x, Scalar(0.0), x.dtype(), dx);
}
if (ddout && !ddx && !ddy) {
FullLikeKernel<T, Context>(
dev_ctx, dout, Scalar(0.0), dout.dtype(), ddout);
}
if (dx) {
if (dx_dims != x_help.dims()) {
dx->Resize(dx_dims);
}
}
if (dy) {
if (dy_dims != y_help.dims()) {
dy->Resize(dy_dims);
}
}
if (ddout) {
if (ddout_dims != dout_help.dims()) {
ddout->Resize(ddout_dims);
}
}
} else {
// Case3: broadcast. It need cost much time to reduce sum for the
// broadcast and wastes the memory.
// So we should avoid the case in reality.
VLOG(3) << "It need cost much time to reduce sum for the broadcast and "
"wastes the memory. So we should avoid the case in reality";
if (dx || dy) {
dout_conj = Conj<T>(dev_ctx, dout);
}
if (ddout) {
x_conj = Conj<T>(dev_ctx, x);
y_conj = Conj<T>(dev_ctx, y);
}
DenseTensor dx_help;
DenseTensor dy_help;
if (transpose_x) {
if (transpose_y) {
if (dx && ddy) {
MatMulFunction<Context, T>(dev_ctx,
ddy.get(),
dout_conj,
y_dims,
dout_dims,
&dx_help,
true,
true);
}
if (dy && ddx) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
ddx.get(),
dout_dims,
x_dims,
&dy_help,
true,
true);
}
} else {
if (dx && ddy) {
MatMulFunction<Context, T>(dev_ctx,
ddy.get(),
dout_conj,
y_dims,
dout_dims,
&dx_help,
false,
true);
}
if (dy && ddx) {
MatMulFunction<Context, T>(dev_ctx,
ddx.get(),
dout_conj,
x_dims,
dout_dims,
&dy_help,
false,
false);
}
}
} else {
if (transpose_y) {
if (dx && ddy) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
ddy.get(),
dout_dims,
y_dims,
&dx_help,
false,
false);
}
if (dy && ddx) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
ddx.get(),
dout_dims,
x_dims,
&dy_help,
true,
false);
}
} else {
if (dx && ddy) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
ddy.get(),
dout_dims,
y_dims,
&dx_help,
false,
true);
}
if (dy && ddx) {
MatMulFunction<Context, T>(dev_ctx,
ddx.get(),
dout_conj,
x_dims,
dout_dims,
&dy_help,
true,
false);
}
}
}
// get help dims
const std::vector<std::int64_t> dx_help_dims = vectorize(dx_help.dims());
const std::vector<std::int64_t> dy_help_dims = vectorize(dy_help.dims());
std::vector<std::int64_t> dx_broadcast_dims(ndim);
std::vector<std::int64_t> dy_broadcast_dims(ndim);
std::fill(
dx_broadcast_dims.data(), dx_broadcast_dims.data() + ndim - x_ndim, 1);
std::fill(
dy_broadcast_dims.data(), dy_broadcast_dims.data() + ndim - y_ndim, 1);
std::copy(x_dims.data(),
x_dims.data() + x_ndim,
dx_broadcast_dims.data() + ndim - x_ndim);
std::copy(y_dims.data(),
y_dims.data() + y_ndim,
dy_broadcast_dims.data() + ndim - y_ndim);
std::vector<int> dx_reduce_dims;
std::vector<int> dy_reduce_dims;
for (int idx = 0; idx <= ndim - 3; idx++) {
if (dx_help_dims[idx] != 1 && dx_broadcast_dims[idx] == 1) {
dx_reduce_dims.push_back(idx);
}
if (dy_help_dims[idx] != 1 && dy_broadcast_dims[idx] == 1) {
dy_reduce_dims.push_back(idx);
}
}
// Reduce sum to get grad by ReduceSum
if (dx && dx_help.initialized()) {
if (dx_reduce_dims.empty()) {
*dx = std::move(dx_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, dx_help, dx, dx_reduce_dims);
}
dx->Resize(x.dims());
} else if (dx && !dx_help.initialized()) {
FullLikeKernel<T, Context>(dev_ctx, x, Scalar(0.0), x.dtype(), dx);
}
if (dy && dy_help.initialized()) {
if (dy_reduce_dims.empty()) {
*dy = std::move(dy_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, dy_help, dy, dy_reduce_dims);
}
dy->Resize(y.dims());
} else if (dy && !dy_help.initialized()) {
FullLikeKernel<T, Context>(dev_ctx, y, Scalar(0.0), y.dtype(), dy);
}
if (ddout) {
// Calculate the gradient of OutputGrad(Out)
if (ddx) {
MatMulFunction<Context, T>(dev_ctx,
ddx.get(),
y_conj,
x_dims,
y_dims,
ddout,
transpose_x,
transpose_y);
}
if (ddy) {
MatMulFunction<Context, T>(dev_ctx,
x_conj,
ddy.get(),
x_dims,
y_dims,
ddout,
transpose_x,
transpose_y,
true);
}
}
}
}
template <typename T, typename Context>
void MatmulTripleGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const DenseTensor& dout,
const optional<DenseTensor>& ddx,
const optional<DenseTensor>& ddy,
const optional<DenseTensor>& d_dx,
const optional<DenseTensor>& d_dy,
const optional<DenseTensor>& d_ddout,
bool transpose_x,
bool transpose_y,
DenseTensor* out_d_x,
DenseTensor* out_d_y,
DenseTensor* out_d_dout,
DenseTensor* out_d_ddx,
DenseTensor* out_d_ddy) {
// Get dims from the input x, y, output_grad
std::vector<std::int64_t> x_dims = vectorize(x.dims());
std::vector<std::int64_t> y_dims = vectorize(y.dims());
std::vector<std::int64_t> dout_dims = vectorize(dout.dims());
int x_ndim = x_dims.size();
int y_ndim = y_dims.size();
int ndim = dout_dims.size();
// Case1 : x's and y's dim = 1
if (x_ndim == 1 && y_ndim == 1) {
VLOG(3) << "======== MatMulV2TripleGradKernel, Compute ====== Case 1";
DotTripleGradFunction<Context, T>()(dev_ctx,
&x,
&y,
&dout,
&ddx,
&ddy,
&d_dx,
&d_dy,
&d_ddout,
out_d_x,
out_d_y,
out_d_dout,
out_d_ddx,
out_d_ddy);
return;
}
DenseTensor x_conj;
DenseTensor y_conj;
DenseTensor dout_conj;
DenseTensor ddx_conj;
DenseTensor ddy_conj;
bool is_broadcast = true;
if (x_ndim <= 2 || y_ndim <= 2) {
is_broadcast = false;
} else if (x_ndim != y_ndim) {
is_broadcast = true;
} else {
is_broadcast = !std::equal(
x_dims.cbegin(), x_dims.cbegin() + x_ndim - 2, y_dims.cbegin());
}
if (!is_broadcast) {
// Case2: no broadcast or no batch size
VLOG(3) << "======== MatMulV2TripleGradKernel, Compute ====== Case 2";
DenseTensor x_help = x;
DenseTensor y_help = y;
DenseTensor dout_help = dout;
DenseTensor ddx_help;
DenseTensor ddy_help;
ReshapeXYOutIntoMatrixSequence(
&x_help, &y_help, &dout_help, transpose_x, transpose_y);
if (ddx) {
ddx_help = ddx.get();
if (ddx_help.dims() != x_help.dims()) {
ddx_help.Resize(x_help.dims());
}
}
if (ddy) {
ddy_help = ddy.get();
if (ddy_help.dims() != y_help.dims()) {
ddy_help.Resize(y_help.dims());
}
}
DDim out_dx_dims;
if (out_d_x) {
out_dx_dims = out_d_x->dims();
if (out_dx_dims != x_help.dims()) {
out_d_x->Resize(x_help.dims());
}
if (ddy) {
ddy_conj = Conj<T>(dev_ctx, ddy_help);
}
}
DDim out_dy_dims;
if (out_d_y) {
out_dy_dims = out_d_y->dims();
if (out_dy_dims != y_help.dims()) {
out_d_y->Resize(y_help.dims());
}
if (ddx) {
ddx_conj = Conj<T>(dev_ctx, ddx_help);
}
}
DDim out_d_dout_dims;
if (out_d_dout) {
out_d_dout_dims = out_d_dout->dims();
if (out_d_dout_dims != dout_help.dims()) {
out_d_dout->Resize(dout_help.dims());
}
if (ddx && !ddx_conj.IsInitialized()) {
ddx_conj = Conj<T>(dev_ctx, ddx_help);
}
if (ddy && !ddy_conj.IsInitialized()) {
ddy_conj = Conj<T>(dev_ctx, ddy_help);
}
}
DDim out_d_ddx_dims;
if (out_d_ddx) {
out_d_ddx_dims = out_d_ddx->dims();
if (out_d_ddx_dims != x_help.dims()) {
out_d_ddx->Resize(x_help.dims());
}
dout_conj = Conj<T>(dev_ctx, dout_help);
y_conj = Conj<T>(dev_ctx, y_help);
}
DDim out_d_ddy_dims;
if (out_d_ddy) {
out_d_ddy_dims = out_d_ddy->dims();
if (out_d_ddy_dims != y_help.dims()) {
out_d_ddy->Resize(y_help.dims());
}
if (!dout_conj.IsInitialized()) {
dout_conj = Conj<T>(dev_ctx, dout_help);
}
x_conj = Conj<T>(dev_ctx, x_help);
}
bool d_dout_flag = false;
bool d_ddx_flag = false;
bool d_ddy_flag = false;
if (d_ddout) {
auto d_ddout_mat = d_ddout.get();
if (d_ddout_mat.dims() != dout_help.dims()) {
d_ddout_mat.Resize(dout_help.dims());
}
if (out_d_y && ddx) {
if (transpose_x && transpose_y) {
// out_d_y = d_ddout' * ddx'
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
true,
true,
ddx_conj,
true,
false,
out_d_y,
false);
} else if (transpose_x) {
// out_d_y = ddx * d_ddout
CalcInputGrad<T>(dev_ctx,
ddx_conj,
false,
false,
d_ddout_mat,
false,
true,
out_d_y,
false);
} else if (transpose_y) {
// out_d_y = d_ddout' * ddx
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
true,
true,
ddx_conj,
false,
true,
out_d_y,
false);
} else {
// out_d_y = ddx' * d_ddout
CalcInputGrad<T>(dev_ctx,
ddx_conj,
true,
true,
d_ddout_mat,
false,
true,
out_d_y,
false);
}
} else if (out_d_y) {
FullLikeKernel<T, Context>(dev_ctx, y, Scalar(0.0), y.dtype(), out_d_y);
}
if (out_d_x && ddy) {
if (transpose_x && transpose_y) {
// out_d_x = ddy' * d_ddout'
CalcInputGrad<T>(dev_ctx,
ddy_conj,
true,
true,
d_ddout_mat,
true,
false,
out_d_x,
false);
} else if (transpose_x) {
// out_d_x = ddy * d_ddout'
CalcInputGrad<T>(dev_ctx,
ddy_conj,
false,
false,
d_ddout_mat,
true,
false,
out_d_x,
false);
} else if (transpose_y) {
// out_d_x = d_ddout * ddy
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
false,
false,
ddy_conj,
false,
true,
out_d_x,
false);
} else {
// out_d_x = d_ddout * ddy'
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
false,
false,
ddy_conj,
true,
false,
out_d_x,
false);
}
} else if (out_d_x) {
FullLikeKernel<T, Context>(dev_ctx, x, Scalar(0.0), x.dtype(), out_d_x);
}
// equations:
// d_ddx = DOut * D_DY + Y * D_DDOut
// Let: d_ddx1 = Y * D_DDOut
// Let: d_ddx2 = DOut * D_DY
// d_ddy = DOut * D_DX + X * D_DDOut
// Let: d_ddy1 = X * D_DDOut
// Let: d_ddy2 = DOut * D_DX
// d_dout = DDY * D_DX + DDX * D_DY
// Let: d_dout1 = DDX * D_DY
// Let: d_dout2 = DDY * D_DX
// compute d_ddx1
if (out_d_ddx) {
if (transpose_x && transpose_y) {
// out_d_ddx1 = y' * d_ddout'
CalcInputGrad<T>(dev_ctx,
y_conj,
true,
true,
d_ddout_mat,
true,
false,
out_d_ddx,
d_ddx_flag);
} else if (transpose_x) {
// out_d_ddx1 = y * d_ddout'
CalcInputGrad<T>(dev_ctx,
y_conj,
false,
false,
d_ddout_mat,
true,
false,
out_d_ddx,
d_ddx_flag);
} else if (transpose_y) {
// out_d_ddx1 = d_ddout * y
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
false,
false,
y_conj,
false,
true,
out_d_ddx,
d_ddx_flag);
} else {
// out_d_ddx1 = d_ddout * y'
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
false,
false,
y_conj,
true,
false,
out_d_ddx,
d_ddx_flag);
}
d_ddx_flag = true;
}
// compute d_ddy1
if (out_d_ddy) {
if (transpose_x && transpose_y) {
// out_d_ddy1 = d_ddout' * x'
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
true,
true,
x_conj,
true,
false,
out_d_ddy,
false);
} else if (transpose_x) {
// out_d_ddy1 = x * d_ddout
CalcInputGrad<T>(dev_ctx,
x_conj,
false,
false,
d_ddout_mat,
false,
true,
out_d_ddy,
false);
} else if (transpose_y) {
// out_d_ddy1 = d_ddout' * x
CalcInputGrad<T>(dev_ctx,
d_ddout_mat,
true,
true,
x_conj,
false,
true,
out_d_ddy,
false);
} else {
// out_d_ddy1 = x' * d_ddout
CalcInputGrad<T>(dev_ctx,
x_conj,
true,
true,
d_ddout_mat,
false,
true,
out_d_ddy,
false);
}
d_ddy_flag = true;
}
} else {
// d_ddout is none
if (out_d_x) {
FullLikeKernel<T, Context>(dev_ctx, x, Scalar(0.0), x.dtype(), out_d_x);
}
if (out_d_y) {
FullLikeKernel<T, Context>(dev_ctx, y, Scalar(0.0), y.dtype(), out_d_y);
}
}
if (d_dy) {
auto d_dy_mat = d_dy.get();
if (d_dy_mat.dims() != y_help.dims()) {
d_dy_mat.Resize(y_help.dims());
}
// compute d_dout1
if (out_d_dout && ddx) {
CalcInputGrad<T>(dev_ctx,
ddx_conj,
transpose_x,
true,
d_dy_mat,
transpose_y,
false,
out_d_dout,
d_dout_flag);
d_dout_flag = true;
}
// compute d_ddx2
if (out_d_ddx) {
if (transpose_x && transpose_y) {
// out_d_ddx2 = D_DY' * DOut'
CalcInputGrad<T>(dev_ctx,
d_dy_mat,
true,
true,
dout_conj,
true,
false,
out_d_ddx,
d_ddx_flag);
} else if (transpose_x) {
// out_d_ddx2 = D_DY * Dout'
CalcInputGrad<T>(dev_ctx,
d_dy_mat,
false,
false,
dout_conj,
true,
false,
out_d_ddx,
d_ddx_flag);
} else if (transpose_y) {
// out_d_ddx2 = Dout * D_DY
CalcInputGrad<T>(dev_ctx,
dout_conj,
false,
false,
d_dy_mat,
false,
true,
out_d_ddx,
d_ddx_flag);
} else {
// out_d_ddx2 = Dout * D_DY'
CalcInputGrad<T>(dev_ctx,
dout_conj,
false,
false,
d_dy_mat,
true,
false,
out_d_ddx,
d_ddx_flag);
}
}
}
if (d_dx) {
auto d_dx_mat = d_dx.get();
if (d_dx_mat.dims() != x_help.dims()) {
d_dx_mat.Resize(x_help.dims());
}
// compute d_dout2
if (out_d_dout && ddy) {
CalcInputGrad<T>(dev_ctx,
d_dx_mat,
transpose_x,
true,
ddy_conj,
transpose_y,
false,
out_d_dout,
d_dout_flag);
}
// compute d_ddy2
if (out_d_ddy) {
if (transpose_x && transpose_y) {
// out_d_ddy2 = dout' * d_dx'
CalcInputGrad<T>(dev_ctx,
dout_conj,
true,
true,
d_dx_mat,
true,
false,
out_d_ddy,
d_ddy_flag);
} else if (transpose_x) {
// out_d_ddy2 = d_dx * dout
CalcInputGrad<T>(dev_ctx,
d_dx_mat,
false,
false,
dout_conj,
false,
true,
out_d_ddy,
d_ddy_flag);
} else if (transpose_y) {
// out_d_ddy2 = dout' * d_dx
CalcInputGrad<T>(dev_ctx,
dout_conj,
true,
true,
d_dx_mat,
false,
true,
out_d_ddy,
d_ddy_flag);
} else {
// out_d_ddy2 = d_dx' * dout
CalcInputGrad<T>(dev_ctx,
d_dx_mat,
true,
true,
dout_conj,
false,
true,
out_d_ddy,
d_ddy_flag);
}
}
}
if (out_d_x) {
if (out_dx_dims != x_help.dims()) {
out_d_x->Resize(out_dx_dims);
}
}
if (out_d_y) {
if (out_dy_dims != y_help.dims()) {
out_d_y->Resize(out_dy_dims);
}
}
if (out_d_dout) {
if (out_d_dout_dims != dout_help.dims()) {
out_d_dout->Resize(out_d_dout_dims);
}
}
if (out_d_ddx) {
if (out_d_ddx_dims != x_help.dims()) {
out_d_ddx->Resize(out_d_ddx_dims);
}
}
if (out_d_ddy) {
if (out_d_ddy_dims != y_help.dims()) {
out_d_ddy->Resize(out_d_ddy_dims);
}
}
if (out_d_dout && !out_d_dout->IsInitialized()) {
FullLikeKernel<T, Context>(
dev_ctx, dout, Scalar(0.0), dout.dtype(), out_d_dout);
}
if (out_d_ddx && !out_d_ddx->IsInitialized()) {
FullLikeKernel<T, Context>(dev_ctx, x, Scalar(0.0), x.dtype(), out_d_ddx);
}
if (out_d_ddy && !out_d_ddy->IsInitialized()) {
FullLikeKernel<T, Context>(dev_ctx, y, Scalar(0.0), y.dtype(), out_d_ddy);
}
} else {
// Case3: broadcast. It need cost much time to reduce sum for the
// broadcast and wastes the memory.
// So we should avoid the case in reality.
VLOG(3) << "======== MatMulV2TripleGradKernel, Compute ====== Case 3";
VLOG(3) << "It need cost much time to reduce sum for the broadcast and "
"wastes the memory. So we should avoid the case in reality";
DenseTensor out_dx_help;
DenseTensor out_dy_help;
DenseTensor out_d_ddx_help;
DenseTensor out_d_ddy_help;
if (out_d_dout) {
if (ddx) {
ddx_conj = Conj<T>(dev_ctx, ddx.get());
}
if (ddy) {
ddy_conj = Conj<T>(dev_ctx, ddy.get());
}
}
if (out_d_ddx || out_d_ddy) {
x_conj = Conj<T>(dev_ctx, x);
y_conj = Conj<T>(dev_ctx, y);
dout_conj = Conj<T>(dev_ctx, dout);
}
if (transpose_x) {
if (transpose_y) {
// dX = ddY' d_ddout, dY = d_ddout ddX'
if (out_d_x && ddy && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
ddy_conj,
d_ddout.get(),
y_dims,
dout_dims,
&out_dx_help,
true,
true);
if (out_d_y && ddx && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
ddx_conj,
dout_dims,
x_dims,
&out_dy_help,
true,
true);
} else {
// dX = ddY d_ddout', dY = ddX d_ddout
if (out_d_x && ddy && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
ddy_conj,
d_ddout.get(),
y_dims,
dout_dims,
&out_dx_help,
false,
true);
if (out_d_y && ddx && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
ddx_conj,
d_ddout.get(),
x_dims,
dout_dims,
&out_dy_help,
false,
false);
}
} else {
if (transpose_y) {
// dX = d_ddout ddY, dY = d_ddout ddX
if (out_d_x && ddy && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
ddy_conj,
dout_dims,
y_dims,
&out_dx_help,
false,
false);
if (out_d_y && ddx && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
ddx_conj,
dout_dims,
x_dims,
&out_dy_help,
true,
false);
} else {
// dX = d_ddout ddY', dY = ddX' d_ddout
if (out_d_x && ddy && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
ddy_conj,
dout_dims,
y_dims,
&out_dx_help,
false,
true);
if (out_d_y && ddx && d_ddout)
MatMulFunction<Context, T>(dev_ctx,
ddx_conj,
d_ddout.get(),
x_dims,
dout_dims,
&out_dy_help,
true,
false);
}
}
// get help dims
const std::vector<std::int64_t> dx_help_dims =
vectorize(out_dx_help.dims());
const std::vector<std::int64_t> dy_help_dims =
vectorize(out_dx_help.dims());
std::vector<std::int64_t> dx_broadcast_dims(ndim);
std::vector<std::int64_t> dy_broadcast_dims(ndim);
std::fill(
dx_broadcast_dims.data(), dx_broadcast_dims.data() + ndim - x_ndim, 1);
std::fill(
dy_broadcast_dims.data(), dy_broadcast_dims.data() + ndim - y_ndim, 1);
std::copy(x_dims.data(),
x_dims.data() + x_ndim,
dx_broadcast_dims.data() + ndim - x_ndim);
std::copy(y_dims.data(),
y_dims.data() + y_ndim,
dy_broadcast_dims.data() + ndim - y_ndim);
std::vector<int> dx_reduce_dims;
std::vector<int> dy_reduce_dims;
for (int idx = 0; idx <= ndim - 3; idx++) {
if (dx_help_dims[idx] != 1 && dx_broadcast_dims[idx] == 1) {
dx_reduce_dims.push_back(idx);
}
if (dy_help_dims[idx] != 1 && dy_broadcast_dims[idx] == 1) {
dy_reduce_dims.push_back(idx);
}
}
// Reduce sum to get grad by ReduceSum
if (out_d_x && out_dx_help.initialized()) {
if (dx_reduce_dims.empty()) {
*out_d_x = std::move(out_dx_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, out_dx_help, out_d_x, dx_reduce_dims);
}
out_d_x->Resize(x.dims());
} else if (out_d_x) {
FullLikeKernel<T, Context>(dev_ctx, x, Scalar(0.0), x.dtype(), out_d_x);
}
if (out_d_y && out_dy_help.initialized()) {
if (dy_reduce_dims.empty()) {
*out_d_y = std::move(out_dy_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, out_dy_help, out_d_y, dy_reduce_dims);
}
out_d_y->Resize(y.dims());
} else if (out_d_y) {
FullLikeKernel<T, Context>(dev_ctx, y, Scalar(0.0), y.dtype(), out_d_y);
}
// compute d_dout
if (out_d_dout) {
if (d_dx && ddy) {
MatMulFunction<Context, T>(dev_ctx,
d_dx.get(),
ddy_conj,
x_dims,
y_dims,
out_d_dout,
transpose_x,
transpose_y);
}
if (d_dy && ddx) {
MatMulFunction<Context, T>(dev_ctx,
ddx_conj,
d_dy.get(),
x_dims,
y_dims,
out_d_dout,
transpose_x,
transpose_y,
true);
}
if (!out_d_dout->initialized()) {
FullLikeKernel<T, Context>(
dev_ctx, dout, Scalar(0.0), dout.dtype(), out_d_dout);
}
}
// compute d_ddx
if (out_d_ddx) {
if (transpose_x && transpose_y) {
// out_d_ddx1 = y' * d_ddout'
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
y_conj,
d_ddout.get(),
y_dims,
dout_dims,
&out_d_ddx_help,
true,
true);
}
// out_d_ddx2 = D_DY' * DOut'
if (d_dy) {
MatMulFunction<Context, T>(dev_ctx,
d_dy.get(),
dout_conj,
y_dims,
dout_dims,
&out_d_ddx_help,
true,
true,
true);
}
} else if (transpose_x) {
// out_d_ddx1 = y * d_ddout'
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
y_conj,
d_ddout.get(),
y_dims,
dout_dims,
&out_d_ddx_help,
false,
true);
}
// out_d_ddx2 = D_DY * Dout'
if (d_dy) {
MatMulFunction<Context, T>(dev_ctx,
d_dy.get(),
dout_conj,
y_dims,
dout_dims,
&out_d_ddx_help,
false,
true,
true);
}
} else if (transpose_y) {
// out_d_ddx1 = d_ddout * y
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
y_conj,
dout_dims,
y_dims,
&out_d_ddx_help,
false,
false);
}
// out_d_ddx2 = Dout * D_DY
if (d_dy) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
d_dy.get(),
dout_dims,
y_dims,
&out_d_ddx_help,
false,
false,
true);
}
} else {
// out_d_ddx1 = d_ddout * y'
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
y_conj,
dout_dims,
y_dims,
&out_d_ddx_help,
false,
true);
}
// out_d_ddx2 = Dout * D_DY'
if (d_dy) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
d_dy.get(),
dout_dims,
y_dims,
&out_d_ddx_help,
false,
true,
true);
}
}
if (out_d_ddx_help.initialized()) {
if (dx_reduce_dims.empty()) {
*out_d_ddx = std::move(out_d_ddx_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, out_d_ddx_help, out_d_ddx, dx_reduce_dims);
}
} else {
FullLikeKernel<T, Context>(
dev_ctx, x, Scalar(0.0), x.dtype(), out_d_ddx);
}
out_d_ddx->Resize(x.dims());
}
// compute d_ddy
if (out_d_ddy) {
if (transpose_x && transpose_y) {
// out_d_ddy1 = d_ddout' * x'
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
x_conj,
dout_dims,
x_dims,
&out_d_ddy_help,
true,
true);
}
// out_d_ddy2 = dout' * d_dx'
if (d_dx) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
d_dx.get(),
dout_dims,
x_dims,
&out_d_ddy_help,
true,
true,
true);
}
} else if (transpose_x) {
// out_d_ddy1 = x * d_ddout
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
x_conj,
d_ddout.get(),
x_dims,
dout_dims,
&out_d_ddy_help,
false,
false);
}
// out_d_ddy2 = d_dx * dout
if (d_dx) {
MatMulFunction<Context, T>(dev_ctx,
d_dx.get(),
dout_conj,
x_dims,
dout_dims,
&out_d_ddy_help,
false,
false,
true);
}
} else if (transpose_y) {
// out_d_ddy1 = d_ddout' * x
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
d_ddout.get(),
x_conj,
dout_dims,
x_dims,
&out_d_ddy_help,
true,
false);
}
// out_d_ddy2 = dout' * d_dx
if (d_dx) {
MatMulFunction<Context, T>(dev_ctx,
dout_conj,
d_dx.get(),
dout_dims,
x_dims,
&out_d_ddy_help,
true,
false,
true);
}
} else {
// out_d_ddy1 = x' * d_ddout
if (d_ddout) {
MatMulFunction<Context, T>(dev_ctx,
x_conj,
d_ddout.get(),
x_dims,
dout_dims,
&out_d_ddy_help,
true,
false);
}
// out_d_ddy2 = d_dx' * dout
if (d_dx) {
MatMulFunction<Context, T>(dev_ctx,
d_dx.get(),
dout_conj,
x_dims,
dout_dims,
&out_d_ddy_help,
true,
false,
true);
}
}
if (out_d_ddy_help.initialized()) {
if (dy_reduce_dims.empty()) {
*out_d_ddy = std::move(out_d_ddy_help);
} else {
ReduceSumForMatmulGrad<Context, T>()(
dev_ctx, out_d_ddy_help, out_d_ddy, dy_reduce_dims);
}
} else {
FullLikeKernel<T, Context>(
dev_ctx, y, Scalar(0.0), y.dtype(), out_d_ddy);
}
out_d_ddy->Resize(y.dims());
}
}
}
template <typename T, typename Context>
void MatmulWithFlattenGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const DenseTensor& out_grad,
int x_num_col_dims,
int y_num_col_dims,
DenseTensor* x_grad,
DenseTensor* y_grad) {
auto x_matrix = x.dims().size() > 2 ? ReshapeToMatrix(x, x_num_col_dims) : x;
auto y_matrix = y.dims().size() > 2 ? ReshapeToMatrix(y, y_num_col_dims) : y;
auto* dout = &out_grad;
DenseTensor dout_mat(*dout);
dout_mat.Resize({common::flatten_to_2d(x.dims(), x_num_col_dims)[0],
common::flatten_to_2d(y.dims(), y_num_col_dims)[1]});
auto* dx = x_grad;
auto* dy = y_grad;
if (dx != nullptr) {
dx->set_lod(x.lod());
}
if (dy != nullptr) {
dy->set_lod(y.lod());
}
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
if (dx) {
dev_ctx.template Alloc<T>(dx);
DenseTensor dx_matrix =
dx->dims().size() > 2 ? ReshapeToMatrix(*dx, x_num_col_dims) : *dx;
// dx = dout * y'. dx: M x K, dout : M x N, y : K x N
blas.MatMul(dout_mat, false, y_matrix, true, &dx_matrix);
}
if (dy) {
dev_ctx.template Alloc<T>(dy);
DenseTensor dy_matrix =
dy->dims().size() > 2 ? ReshapeToMatrix(*dy, y_num_col_dims) : *dy;
// dy = x' * dout. dy K x N, dout : M x N, x : M x K
blas.MatMul(x_matrix, true, dout_mat, false, &dy_matrix);
}
}
template <typename T, typename Context>
void MatmulWithFlattenDoubleGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const DenseTensor& out_grad,
const optional<DenseTensor>& x_grad_grad,
const optional<DenseTensor>& y_grad_grad,
int x_num_col_dims,
int y_num_col_dims,
DenseTensor* x_grad,
DenseTensor* y_grad,
DenseTensor* out_grad_grad) {
auto x_mat = x.dims().size() > 2 ? ReshapeToMatrix(x, x_num_col_dims) : x;
auto y_mat = y.dims().size() > 2 ? ReshapeToMatrix(y, y_num_col_dims) : y;
const int64_t m = common::flatten_to_2d(x.dims(), x_num_col_dims)[0];
const int64_t n = common::flatten_to_2d(y.dims(), y_num_col_dims)[1];
auto* dout = &out_grad;
DenseTensor dout_mat(*dout);
dout_mat.Resize({m, n});
auto* ddx = x_grad_grad.get_ptr();
auto* ddy = y_grad_grad.get_ptr();
auto* dx = x_grad;
auto* dy = y_grad;
auto* ddout = out_grad_grad;
DenseTensor ddout_mat;
if (ddout) {
ddout->set_lod(dout->lod());
// allocate and reshape ddout
dev_ctx.template Alloc<T>(ddout);
ddout_mat.ShareDataWith(*ddout);
ddout_mat.Resize({m, n});
}
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
// a flag to specify whether ddout value has been set, if flag
// is false, MatMul beta should be 0 to set ddout, if flag is
// true, MatMul beta should be 1 to add result to ddout.
bool ddout_flag = false;
if (ddx) {
auto ddx_mat = ddx->dims().size() > 2
? ReshapeToMatrix(*ddx, x_num_col_dims)
: static_cast<const DenseTensor&>(*ddx);
// dy = ddx' * dout. dy : K x M, ddx' : K x M, dout : M x N
if (dy) {
dy->set_lod(y.lod());
// allocate and reshape dy
dev_ctx.template Alloc<T>(dy);
DenseTensor dy_mat =
dy->dims().size() > 2 ? ReshapeToMatrix(*dy, y_num_col_dims) : *dy;
blas.MatMul(ddx_mat, true, dout_mat, false, &dy_mat);
}
// ddout1 = ddx * y. ddx : M x K, y : K x N, ddout1 : M x N
if (ddout) {
blas.MatMul(ddx_mat,
false,
y_mat,
false,
static_cast<T>(1.0),
&ddout_mat,
static_cast<T>(ddout_flag));
ddout_flag = true;
}
}
if (ddy) {
auto ddy_mat = ddy->dims().size() > 2
? ReshapeToMatrix(*ddy, y_num_col_dims)
: static_cast<const DenseTensor&>(*ddy);
// dx = dout * ddy'. dout : M x N, ddy' : N x K, dx : M x K
if (dx) {
dx->set_lod(x.lod());
// allocate and reshape dx
dev_ctx.template Alloc<T>(dx);
DenseTensor dx_mat =
dx->dims().size() > 2 ? ReshapeToMatrix(*dx, x_num_col_dims) : *dx;
blas.MatMul(dout_mat, false, ddy_mat, true, &dx_mat);
}
// ddout2 = x * ddy. x : M x K, ddy : K x N, ddout2 : M x N
if (ddout) {
blas.MatMul(x_mat,
false,
ddy_mat,
false,
static_cast<T>(1.0),
&ddout_mat,
static_cast<T>(ddout_flag));
}
}
}
template <typename T, typename Context>
void LegacyMatmulGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const DenseTensor& out_grad,
bool transpose_x,
bool transpose_y,
float alpha,
DenseTensor* dx,
DenseTensor* dy) {
MatmulGradKernel<T, Context>(
dev_ctx, x, y, out_grad, transpose_x, transpose_y, dx, dy);
if (std::fabs(alpha - 1.f) > 1e-6f) {
ScaleKernel<T, Context>(dev_ctx, *dx, Scalar(alpha), Scalar(0), false, dx);
ScaleKernel<T, Context>(dev_ctx, *dy, Scalar(alpha), Scalar(0), false, dy);
}
}
} // namespace phi