// 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 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(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(&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(1.0); blas.MatMul(matrix_a.data(), mat_dim_a, matrix_b.data(), mat_dim_b, alpha, matrix_c.data(), 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 inline DenseTensor MatChainMul(const Context& dev_ctx, const std::vector& ins, const std::vector& ins_dims, const std::vector& order, const uint64_t i, const uint64_t j, const bool save_result, std::vector* results) { if (i == j) { return *ins[i]; } const auto A = MatChainMul(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(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(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 std::vector GetOrder(const std::vector& ins, const std::vector& ins_dims) { uint64_t n = ins.size(); // p: save the ins shape, the ins[i] shape is (p[i], p[i+1]) std::vector 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 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 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::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 static inline DenseTensor MultiDotMatChainOrder( const Context& dev_ctx, const std::vector& ins, const std::vector& ins_dims, const bool save_result, std::vector* results) { auto order = GetOrder(ins, ins_dims); return MatChainMul( dev_ctx, ins, ins_dims, order, 0, ins.size() - 1, save_result, results); } template inline void GetDims(const std::vector& ins, std::vector* 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 void MultiDotKernel(const Context& dev_ctx, const std::vector& x, DenseTensor* out) { auto ins = x; dev_ctx.template Alloc(out); auto blas = funcs::GetBlas(dev_ctx); auto n = ins.size(); std::vector ins_dims(n); GetDims(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(dev_ctx, out->dims(), 0, out); } return; } const T scale = static_cast(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(&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(&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 results; const auto tmp = MultiDotMatChainOrder( 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 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(1.0); auto blas = funcs::GetBlas(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 void MatChainMulGrad(const Context& dev_ctx, const DenseTensor& dout, std::vector* dx, const std::vector& ins, const DDim& dout_dim, const std::vector& ins_dims, const std::vector& order, const uint64_t i, const uint64_t j, const std::vector& 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(&dA); dev_ctx.template Alloc(&dB); CalcGrad(dev_ctx, dout, *A, *B, dout_dim, a_dim, b_dim, &dA, &dB); MatChainMulGrad( dev_ctx, dA, dx, ins, dA.dims(), ins_dims, order, i, right, results); MatChainMulGrad( dev_ctx, dB, dx, ins, dB.dims(), ins_dims, order, left, j, results); } template void MultiDotGradMatChainOrder(const Context& dev_ctx, const DenseTensor& dout, const std::vector& ins, const DDim& dout_dim, const std::vector& ins_dims, std::vector* dx) { auto order = GetOrder(ins, ins_dims); auto n = ins.size(); std::vector results(static_cast(n) * n); MatChainMul( dev_ctx, ins, ins_dims, order, 0, n - 1, true, &results); MatChainMulGrad( dev_ctx, dout, dx, ins, dout_dim, ins_dims, order, 0, n - 1, results); } template void MultiDotGradKernel(const Context& dev_ctx, const std::vector& x, const DenseTensor& out_grad, std::vector x_grad) { auto ins = x; auto dout = out_grad; auto dx = x_grad; auto blas = funcs::GetBlas(dev_ctx); bool size_0 = false; const auto n = ins.size(); for (size_t i = 0; i < n; i++) { dev_ctx.template Alloc(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(dev_ctx, dx[i]->dims(), 0, dx[i]); } } return; } std::vector ins_dims(n); GetDims(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(1); auto mat_dim_dout = funcs::CreateMatrixDescriptor(dout_dim, 0, false); if (n == 2) { CalcGrad(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(&tmp_out); tmp_dout.Resize({mat_dim_dout.height_, Nb}); dev_ctx.template Alloc(&tmp_dout); blas.MatMul( *ins[0], mat_dim_a, *ins[1], mat_dim_b, alpha, &tmp_out, T(0)); CalcGrad(dev_ctx, dout, tmp_out, *ins[2], dout_dim, tmp_out.dims(), ins_dims[2], &tmp_dout, dx[2]); CalcGrad(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(&tmp_out); tmp_dout.Resize({Ka, mat_dim_dout.width_}); dev_ctx.template Alloc(&tmp_dout); blas.MatMul( *ins[1], mat_dim_b, *ins[2], mat_dim_c, alpha, &tmp_out, T(0)); CalcGrad(dev_ctx, dout, *ins[0], tmp_out, dout_dim, ins_dims[0], tmp_dout.dims(), dx[0], &tmp_dout); CalcGrad(dev_ctx, tmp_dout, *ins[1], *ins[2], tmp_dout.dims(), ins_dims[1], ins_dims[2], dx[1], dx[2]); } } else { MultiDotGradMatChainOrder( 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