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

473 lines
16 KiB
C++

// 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 "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/funcs/blas/blas.h"
namespace phi {
template <typename Context, typename T>
inline DenseTensor MatMul(const Context& dev_ctx,
const DenseTensor& matrix_a,
const DenseTensor& matrix_b,
const DDim& a_dim,
const DDim& b_dim) {
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
DenseTensor matrix_c;
DDim c_dim = make_ddim({a_dim[0], b_dim[1]});
matrix_c.Resize(c_dim);
dev_ctx.template Alloc<T>(&matrix_c);
auto mat_dim_a = funcs::CreateMatrixDescriptor(a_dim, 0, false);
auto mat_dim_b = funcs::CreateMatrixDescriptor(b_dim, 0, false);
const T alpha = static_cast<T>(1.0);
blas.MatMul(matrix_a.data<T>(),
mat_dim_a,
matrix_b.data<T>(),
mat_dim_b,
alpha,
matrix_c.data<T>(),
T(0));
return matrix_c;
}
/**
* @brief Recursively calculate matrix multiplication according to the optimal
* order
* Let k = order[i,j], then ins[i...j] = ins[i...k] * ins[k+1 ...j]
*
* @param
* ins: the input tensors
* ins_dims: the shape of ins after reshape
* order: the optimal order
* i: the left of sub chain
* j: the right of sub chain
* save_result: set true by backward
* results: save the intermediate result during backward
*/
template <typename Context, typename T>
inline DenseTensor MatChainMul(const Context& dev_ctx,
const std::vector<const DenseTensor*>& ins,
const std::vector<DDim>& ins_dims,
const std::vector<uint64_t>& order,
const uint64_t i,
const uint64_t j,
const bool save_result,
std::vector<DenseTensor>* results) {
if (i == j) {
return *ins[i];
}
const auto A = MatChainMul<Context, T>(dev_ctx,
ins,
ins_dims,
order,
i,
order[i * ins.size() + j],
save_result,
results);
DDim a_dim = A.dims();
if (i == order[i * ins.size() + j]) {
a_dim = ins_dims[i];
}
const auto B = MatChainMul<Context, T>(dev_ctx,
ins,
ins_dims,
order,
order[i * ins.size() + j] + 1,
j,
save_result,
results);
DDim b_dim = B.dims();
if (j == order[i * ins.size() + j] + 1) {
b_dim = ins_dims[j];
}
auto result = MatMul<Context, T>(dev_ctx, A, B, a_dim, b_dim);
if (save_result) {
(*results)[i * ins.size() + j] = result;
}
return result;
}
/**
* @brief get the optimal order
*/
template <typename Context, typename T>
std::vector<uint64_t> GetOrder(const std::vector<const DenseTensor*>& ins,
const std::vector<DDim>& ins_dims) {
uint64_t n = ins.size();
// p: save the ins shape, the ins[i] shape is (p[i], p[i+1])
std::vector<uint64_t> p(n + 1);
for (uint64_t i = 0; i < n; i++) {
p[i] = ins_dims[i][0];
}
p[n] = ins_dims[n - 1][1];
// m[i, j]: save the lowest cost for multiplying ins[i...j]
std::vector<uint64_t> m(n * n, 0);
// define ins[i...j] means multiplying matrices from ins[i] to ins[j]
// order[i, j] = k, this means that ins[i...k] and ins[k...j] first and then
// multiply the resulting matrices is the optimal order for ins[i...j]
std::vector<uint64_t> order(n * n);
for (uint64_t l = 1; l < n; l++) {
for (uint64_t i = 0; i < n - l; i++) {
auto j = i + l;
m[i * n + j] = std::numeric_limits<uint64_t>::max();
for (uint64_t k = i; k < j; k++) {
uint64_t q =
m[i * n + k] + m[(k + 1) * n + j] + p[i] * p[k + 1] * p[j + 1];
if (q < m[i * n + j]) {
m[i * n + j] = q;
order[i * n + j] = k;
}
}
}
}
return order;
}
template <typename Context, typename T>
static inline DenseTensor MultiDotMatChainOrder(
const Context& dev_ctx,
const std::vector<const DenseTensor*>& ins,
const std::vector<DDim>& ins_dims,
const bool save_result,
std::vector<DenseTensor>* results) {
auto order = GetOrder<Context, T>(ins, ins_dims);
return MatChainMul<Context, T>(
dev_ctx, ins, ins_dims, order, 0, ins.size() - 1, save_result, results);
}
template <typename Context, typename T>
inline void GetDims(const std::vector<const DenseTensor*>& ins,
std::vector<DDim>* ins_dims) {
const auto n = ins.size();
for (size_t i = 0; i < n; i++) {
(*ins_dims)[i] = ins[i]->dims();
if (i == 0 && (*ins_dims)[i].size() == 1) {
(*ins_dims)[i] = make_ddim({1, (*ins_dims)[i][0]});
} else if (i == n - 1 && (*ins_dims)[i].size() == 1) {
(*ins_dims)[i] = make_ddim({(*ins_dims)[i][0], 1});
}
}
}
template <typename T, typename Context>
void MultiDotKernel(const Context& dev_ctx,
const std::vector<const DenseTensor*>& x,
DenseTensor* out) {
auto ins = x;
dev_ctx.template Alloc<T>(out);
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
auto n = ins.size();
std::vector<DDim> ins_dims(n);
GetDims<Context, T>(ins, &ins_dims);
// If any numel is 0, then return.
bool size_0 = false;
for (size_t i = 0; i < n; i++) {
if (x[i]->numel() == 0) size_0 = true;
}
if (size_0) {
// For example: [2, 0], [0, 4] -> [2, 4]
if (out && out->numel() > 0) {
Full<T, Context>(dev_ctx, out->dims(), 0, out);
}
return;
}
const T scale = static_cast<T>(1.0);
if (n == 2) {
auto mat_dim_a = funcs::CreateMatrixDescriptor(ins_dims[0], 0, false);
auto mat_dim_b = funcs::CreateMatrixDescriptor(ins_dims[1], 0, false);
blas.MatMul(*ins[0], mat_dim_a, *ins[1], mat_dim_b, scale, out, T(0));
} else if (n == 3) {
const auto Ma = ins_dims[0][0];
const auto Ka = ins_dims[0][1];
const auto Nb = ins_dims[1][1];
const auto Nc = ins_dims[2][1];
const uint64_t cost1 = Ma * Nb * (Ka + Nc);
const uint64_t cost2 = Ka * Nc * (Nb + Ma);
auto mat_dim_a = funcs::CreateMatrixDescriptor(ins_dims[0], 0, false);
auto mat_dim_b = funcs::CreateMatrixDescriptor(ins_dims[1], 0, false);
auto mat_dim_c = funcs::CreateMatrixDescriptor(ins_dims[2], 0, false);
if (cost1 < cost2) {
DenseTensor tmp_out;
DDim tmp_dim = make_ddim({Ma, Nb});
tmp_out.Resize(tmp_dim);
dev_ctx.template Alloc<T>(&tmp_out);
blas.MatMul(
*ins[0], mat_dim_a, *ins[1], mat_dim_b, scale, &tmp_out, T(0));
auto mat_dim_tmp = funcs::CreateMatrixDescriptor(tmp_dim, 0, false);
blas.MatMul(tmp_out, mat_dim_tmp, *ins[2], mat_dim_c, scale, out, T(0));
} else {
DenseTensor tmp_out;
DDim tmp_dim = make_ddim({Ka, Nc});
tmp_out.Resize(tmp_dim);
dev_ctx.template Alloc<T>(&tmp_out);
blas.MatMul(
*ins[1], mat_dim_b, *ins[2], mat_dim_c, scale, &tmp_out, T(0));
auto mat_dim_tmp = funcs::CreateMatrixDescriptor(tmp_dim, 0, false);
blas.MatMul(*ins[0], mat_dim_a, tmp_out, mat_dim_tmp, scale, out, T(0));
}
} else {
std::vector<DenseTensor> results;
const auto tmp = MultiDotMatChainOrder<Context, T>(
dev_ctx, ins, ins_dims, false, &results);
auto out_dim = out->dims();
*out = tmp;
out->Resize(out_dim);
}
}
/**
* @brief calculate dA and dB
* dA = dout * transpose(B)
* dB = transpose(A) * dout
*/
template <typename Context, typename T>
void CalcGrad(const Context& dev_ctx,
const DenseTensor& dout,
const DenseTensor& A,
const DenseTensor& B,
const DDim& dout_dim,
const DDim& a_dim,
const DDim& b_dim,
DenseTensor* dA,
DenseTensor* dB) {
auto mat_dim_dout = funcs::CreateMatrixDescriptor(dout_dim, 0, false);
auto mat_dim_a = funcs::CreateMatrixDescriptor(a_dim, 0, true);
auto mat_dim_b = funcs::CreateMatrixDescriptor(b_dim, 0, true);
T alpha = static_cast<T>(1.0);
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
blas.MatMul(A, mat_dim_a, dout, mat_dim_dout, alpha, dB, T(0));
blas.MatMul(dout, mat_dim_dout, B, mat_dim_b, alpha, dA, T(0));
}
/**
* @brief calculate multi matrix multiplication grad by a chain order
* @param
* dout: the grad of multi matrix multiplication out
* dx: the out grad of inputs
* ins: the input tensors
* ins_dims: the shape of ins after reshape
* order: the optimal order
* i: the left of sub chain
* j: the right of sub chain
* results: the intermediate result of forward
*/
template <typename Context, typename T>
void MatChainMulGrad(const Context& dev_ctx,
const DenseTensor& dout,
std::vector<DenseTensor*>* dx,
const std::vector<const DenseTensor*>& ins,
const DDim& dout_dim,
const std::vector<DDim>& ins_dims,
const std::vector<uint64_t>& order,
const uint64_t i,
const uint64_t j,
const std::vector<DenseTensor>& results) {
if (i == j) {
*((*dx)[i]) = dout;
return;
}
const auto n = ins.size();
const auto right = order[i * n + j];
const auto left = order[i * n + j] + 1;
// get the multi result of left sub chain
const auto* A = &results[i * n + right];
DDim a_dim = A->dims();
if (i == right) {
A = ins[i];
a_dim = ins_dims[i];
}
// get the multi result of right sub chain
const auto* B = &results[left * n + j];
DDim b_dim = B->dims();
if (left == j) {
B = ins[j];
b_dim = ins_dims[j];
}
DenseTensor dA, dB;
dA.Resize({dout_dim[0], b_dim[0]});
dB.Resize({a_dim[1], dout_dim[1]});
dev_ctx.template Alloc<T>(&dA);
dev_ctx.template Alloc<T>(&dB);
CalcGrad<Context, T>(dev_ctx, dout, *A, *B, dout_dim, a_dim, b_dim, &dA, &dB);
MatChainMulGrad<Context, T>(
dev_ctx, dA, dx, ins, dA.dims(), ins_dims, order, i, right, results);
MatChainMulGrad<Context, T>(
dev_ctx, dB, dx, ins, dB.dims(), ins_dims, order, left, j, results);
}
template <typename Context, typename T>
void MultiDotGradMatChainOrder(const Context& dev_ctx,
const DenseTensor& dout,
const std::vector<const DenseTensor*>& ins,
const DDim& dout_dim,
const std::vector<DDim>& ins_dims,
std::vector<DenseTensor*>* dx) {
auto order = GetOrder<Context, T>(ins, ins_dims);
auto n = ins.size();
std::vector<DenseTensor> results(static_cast<int64_t>(n) * n);
MatChainMul<Context, T>(
dev_ctx, ins, ins_dims, order, 0, n - 1, true, &results);
MatChainMulGrad<Context, T>(
dev_ctx, dout, dx, ins, dout_dim, ins_dims, order, 0, n - 1, results);
}
template <typename T, typename Context>
void MultiDotGradKernel(const Context& dev_ctx,
const std::vector<const DenseTensor*>& x,
const DenseTensor& out_grad,
std::vector<DenseTensor*> x_grad) {
auto ins = x;
auto dout = out_grad;
auto dx = x_grad;
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
bool size_0 = false;
const auto n = ins.size();
for (size_t i = 0; i < n; i++) {
dev_ctx.template Alloc<T>(dx[i]);
if (dx[i]->numel() == 0) {
size_0 = true;
}
}
if (size_0) {
for (size_t i = 0; i < n; i++) {
if (dx[i]->numel() > 0) {
Full<T, Context>(dev_ctx, dx[i]->dims(), 0, dx[i]);
}
}
return;
}
std::vector<DDim> ins_dims(n);
GetDims<Context, T>(ins, &ins_dims);
DDim dout_dim = dout.dims();
if (ins[0]->dims().size() == 1 && ins[n - 1]->dims().size() == 1) {
dout_dim = make_ddim({1, 1});
} else if (ins[0]->dims().size() == 1) {
if (dout_dim.size() == 1) {
dout_dim = make_ddim({1, dout_dim[0]});
}
} else if (ins[n - 1]->dims().size() == 1) {
if (dout_dim.size() == 1) {
dout_dim = make_ddim({dout_dim[0], 1});
}
}
T alpha = static_cast<T>(1);
auto mat_dim_dout = funcs::CreateMatrixDescriptor(dout_dim, 0, false);
if (n == 2) {
CalcGrad<Context, T>(dev_ctx,
dout,
*ins[0],
*ins[1],
dout_dim,
ins_dims[0],
ins_dims[1],
dx[0],
dx[1]);
} else if (n == 3) {
const auto Ma = ins_dims[0][0];
const auto Ka = ins_dims[0][1];
const auto Nb = ins_dims[1][1];
const auto Nc = ins_dims[2][1];
const uint64_t cost1 = Ma * Nb * (Ka + Nc);
const uint64_t cost2 = Ka * Nc * (Nb + Ma);
auto mat_dim_a = funcs::CreateMatrixDescriptor(ins_dims[0], 0, false);
auto mat_dim_b = funcs::CreateMatrixDescriptor(ins_dims[1], 0, false);
auto mat_dim_c = funcs::CreateMatrixDescriptor(ins_dims[2], 0, false);
if (cost1 < cost2) {
DenseTensor tmp_out, tmp_dout;
tmp_out.Resize({Ma, Nb});
dev_ctx.template Alloc<T>(&tmp_out);
tmp_dout.Resize({mat_dim_dout.height_, Nb});
dev_ctx.template Alloc<T>(&tmp_dout);
blas.MatMul(
*ins[0], mat_dim_a, *ins[1], mat_dim_b, alpha, &tmp_out, T(0));
CalcGrad<Context, T>(dev_ctx,
dout,
tmp_out,
*ins[2],
dout_dim,
tmp_out.dims(),
ins_dims[2],
&tmp_dout,
dx[2]);
CalcGrad<Context, T>(dev_ctx,
tmp_dout,
*ins[0],
*ins[1],
tmp_dout.dims(),
ins_dims[0],
ins_dims[1],
dx[0],
dx[1]);
} else {
DenseTensor tmp_out, tmp_dout;
tmp_out.Resize({Ka, Nc});
dev_ctx.template Alloc<T>(&tmp_out);
tmp_dout.Resize({Ka, mat_dim_dout.width_});
dev_ctx.template Alloc<T>(&tmp_dout);
blas.MatMul(
*ins[1], mat_dim_b, *ins[2], mat_dim_c, alpha, &tmp_out, T(0));
CalcGrad<Context, T>(dev_ctx,
dout,
*ins[0],
tmp_out,
dout_dim,
ins_dims[0],
tmp_dout.dims(),
dx[0],
&tmp_dout);
CalcGrad<Context, T>(dev_ctx,
tmp_dout,
*ins[1],
*ins[2],
tmp_dout.dims(),
ins_dims[1],
ins_dims[2],
dx[1],
dx[2]);
}
} else {
MultiDotGradMatChainOrder<Context, T>(
dev_ctx, dout, ins, dout_dim, ins_dims, &dx);
// if x's shape is: [3] [3, 4] [4]
// dx's shape will be: [1, 3] [3, 4] [4, 1]
if (ins[n - 1]->dims().size() == 1) {
dx[n - 1]->Resize({dx[n - 1]->dims()[0]});
}
if (ins[0]->dims().size() == 1) {
dx[0]->Resize({dx[0]->dims()[1]});
}
}
}
} // namespace phi