Files
paddlepaddle--paddle/paddle/phi/kernels/fusion/onednn/fused_matmul_kernel.cc
T
2026-07-13 12:40:42 +08:00

614 lines
25 KiB
C++

// Copyright (c) 2023 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.
#include <string>
#include "paddle/phi/backends/onednn/matmul_utils.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/core/tensor_utils.h"
using dnnl::memory;
namespace phi {
namespace fusion {
template <typename XT, typename YT, typename OT>
class FusedMatmulOneDNNHandler
: public funcs::OneDNNHandlerNoCachingT<XT, dnnl::matmul> {
public:
FusedMatmulOneDNNHandler(const OneDNNContext &dev_ctx,
const DenseTensor *residual_data,
const std::vector<int64_t> &x_org_dims,
const std::vector<int64_t> &y_org_dims,
bool trans_x,
bool trans_y,
const float matmul_alpha,
const std::vector<int64_t> &x_strides_override,
const std::vector<int64_t> &y_strides_override,
bool is_output_fused,
const std::string &fuse_activation,
const float fuse_alpha,
const float fuse_beta,
const float fused_output_scale,
const float scale_x,
const float scale_y,
const float scale_in_eltwise,
const float scale_out,
const bool force_fp32_output)
: funcs::OneDNNHandlerNoCachingT<XT, dnnl::matmul>(dev_ctx.GetEngine(),
dev_ctx.GetPlace()) {
// M X K * K X N
std::vector<int64_t> x_dims(x_org_dims);
std::vector<int64_t> y_dims(y_org_dims);
const int MB_idx = static_cast<int>(x_dims.size()) - 3;
const int H_idx = static_cast<int>(x_dims.size()) - 2;
const int W_idx = static_cast<int>(x_dims.size()) - 1;
if (trans_x) std::swap(x_dims[H_idx], x_dims[W_idx]);
if (trans_y) std::swap(y_dims[H_idx], y_dims[W_idx]);
const memory::dim M = x_dims[H_idx];
const memory::dim K = x_dims[W_idx];
const memory::dim N = y_dims[W_idx];
std::vector<int64_t> x_strides(x_dims.size() - 3, 1);
std::vector<int64_t> y_strides(x_dims.size() - 3, 1);
std::vector<int64_t> out_strides(x_dims.size() - 3, 1);
std::vector<int64_t> out_ddims(x_dims.size() - 3, 1);
x_strides.reserve(x_dims.size());
y_strides.reserve(x_dims.size());
out_strides.reserve(x_dims.size());
if (x_strides_override.empty()) {
if (trans_x) {
x_strides.insert(x_strides.end(), {M * K, 1, M});
} else {
x_strides.insert(x_strides.end(), {M * K, K, 1});
}
} else {
x_strides = x_strides_override;
}
if (y_strides_override.empty()) {
if (trans_y) {
y_strides.insert(y_strides.end(), {N * K, 1, K});
} else {
y_strides.insert(y_strides.end(), {N * K, N, 1});
}
} else {
y_strides = y_strides_override;
}
out_strides.insert(out_strides.end(), {M * N, N, 1});
out_ddims.insert(out_ddims.end(),
{std::max(x_dims[MB_idx], y_dims[MB_idx]), M, N});
for (int i = static_cast<int>(x_dims.size()) - 4; i >= 0; --i) {
out_ddims[i] = std::max(x_dims[i], y_dims[i]);
if (x_strides_override.empty()) {
x_strides[i] = x_dims[i + 1] * x_strides[i + 1];
}
if (y_strides_override.empty()) {
y_strides[i] = y_dims[i + 1] * y_strides[i + 1];
}
out_strides[i] = out_ddims[i + 1] * out_strides[i + 1];
}
// TODO(jczaja): Why not for int8??
if (!funcs::is_int8<OT>() && is_output_fused) {
std::vector<int> transpose_axis = {0, 2, 1, 3};
out_strides = funcs::FakeTransposeStrides(out_ddims, transpose_axis);
}
auto x_md = memory::desc(x_dims, funcs::OneDNNGetDataType<XT>(), x_strides);
auto y_md = memory::desc(y_dims, funcs::OneDNNGetDataType<YT>(), y_strides);
auto out_md =
memory::desc(out_ddims, funcs::OneDNNGetDataType<OT>(), out_strides);
const auto matmul_attrs = CreateMatmulAttrs(dev_ctx,
residual_data,
matmul_alpha,
fuse_activation,
fuse_alpha,
fuse_beta,
fused_output_scale,
scale_x,
scale_y,
scale_in_eltwise,
scale_out,
force_fp32_output,
out_ddims);
this->AcquireForwardPrimitiveDescriptor(matmul_attrs, x_md, y_md, out_md);
}
dnnl::primitive_attr CreateMatmulAttrs(
const OneDNNContext &dev_ctx,
const DenseTensor *residual_data,
const float matmul_alpha,
const std::string &fuse_activation,
const float fuse_alpha,
const float fuse_beta,
const float fused_output_scale,
const float scale_x,
const float scale_y,
const float scale_in_eltwise,
const float scale_out,
const bool force_fp32_output,
const std::vector<int64_t> &out_ddims) {
dnnl::primitive_attr matmul_attrs;
dnnl::post_ops post_operations;
if (scale_x != 1.0f) {
matmul_attrs.set_scales_mask(DNNL_ARG_SRC, 0);
}
// alpha can be folded to weight scale
if (scale_y != 1.0f || matmul_alpha != 1.0f) {
matmul_attrs.set_scales_mask(DNNL_ARG_WEIGHTS, 0);
}
if (!force_fp32_output && scale_out != 1.0f) {
matmul_attrs.set_scales_mask(DNNL_ARG_DST, 0);
}
if (residual_data) {
// fill 1 in the front of adesc, to make residual ndims to be same as dst
// dims
int dst_size = out_ddims.size();
int origin_size = residual_data->mem_desc().get_ndims();
auto reshaped_md = residual_data->mem_desc();
dnnl::memory::dims expanded_dims = residual_data->mem_desc().get_dims();
if (origin_size < dst_size) {
expanded_dims.insert(expanded_dims.begin(), dst_size - origin_size, 1);
reshaped_md = residual_data->mem_desc().reshape(expanded_dims);
}
auto residual_data_tz = vectorize(residual_data->dims());
auto chosen_memory_format = funcs::OneDNNMemoryFormat::any;
dnnl::memory::desc residual_data_md;
if (!out_ddims.empty() && out_ddims[0] > 1 &&
residual_data_tz.size() == 4 && residual_data_tz[0] == 1 &&
residual_data_tz[1] > 1 && residual_data_tz[2] > 1 &&
residual_data_tz[3] > 1) {
chosen_memory_format = funcs::OneDNNMemoryFormat::nchw;
residual_data_md = memory::desc(
out_ddims, funcs::OneDNNGetDataType<OT>(), chosen_memory_format);
} else {
residual_data_md = reshaped_md;
}
post_operations.append_binary(dnnl::algorithm::binary_add,
residual_data_md);
if (scale_in_eltwise != 0.0f) {
float sum_scale = 1.f / scale_in_eltwise;
post_operations.append_sum(sum_scale);
}
}
funcs::AppendActivation(
dev_ctx, post_operations, fuse_activation, fuse_alpha, fuse_beta);
if (fused_output_scale != 1.0f) {
post_operations.append_eltwise(
dnnl::algorithm::eltwise_linear, fused_output_scale, 0.0f);
}
matmul_attrs.set_post_ops(post_operations);
return matmul_attrs;
}
std::shared_ptr<memory> AcquireWeightsMemory(const DenseTensor *input) {
const YT *input_data = input->data<YT>();
return this->AcquireMemoryFromPrimitive(
this->fwd_pd_->weights_desc(), funcs::to_void_cast<YT>(input_data));
}
std::shared_ptr<dnnl::memory> AcquireSrcMemoryResidual(
const DenseTensor *input) {
const XT *input_data = input->data<XT>();
auto residual_memory_p = this->AcquireMemoryFromPrimitive(
input->mem_desc(), funcs::to_void_cast<XT>(input_data));
return residual_memory_p;
}
std::shared_ptr<dnnl::memory> AcquireSrcMemoryStride(
const DenseTensor *input) {
const XT *input_data = input->data<XT>();
std::shared_ptr<dnnl::memory> src_mem =
this->AcquireMemoryFromPrimitive(this->fwd_pd_->dst_desc());
auto residual_vec = vectorize(input->dims());
int IC = residual_vec[1];
int IH = residual_vec[2];
int IW = residual_vec[3];
size_t size = this->fwd_pd_->dst_desc().get_size() / sizeof(XT);
XT *dst = static_cast<XT *>(src_mem->get_data_handle());
#if defined(_OPENMP)
#pragma omp parallel for
#endif
for (size_t i = 0; i < size; ++i) {
auto mod_i =
static_cast<int>(i - floor(i / (IC * IH * IW)) * (IC * IH * IW));
// Make 1*C*H*W to N*C*H*W to avoid broadcast overhead
dst[i] = input_data[mod_i];
}
return src_mem;
}
std::shared_ptr<dnnl::memory> AcquireDstMemory(const OneDNNContext &dev_ctx,
DenseTensor *output) {
// We cannot use base AcquireDstMemory as it makes an allocation request
// base on DST memory primitive size. This is fine in general, but in MatMul
// we have primitive that covers only one batch of Data and then shift
// pointer for every new batch. Hence DenseTensor size is bigger that
// dst memory primitive size. So would we request less memory that is there
// and it triggers an assertion. So as there is no 'any' format here we can
// leave default size of DenseTensor as computed in ComputeInferShape
OT *ptr = dev_ctx.template Alloc<OT>(output);
return this->AcquireMemoryFromPrimitive(this->fwd_pd_->dst_desc(), ptr);
}
};
template <typename T, typename T_out>
void ExecuteFusedMatmul(const OneDNNContext &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
const DenseTensor *residual_data,
const std::vector<int64_t> &x_dims,
const std::vector<int64_t> &y_dims,
bool trans_x,
bool trans_y,
const float matmul_alpha,
const std::vector<int64_t> &x_strides_override,
const std::vector<int64_t> &y_strides_override,
const bool is_output_fused,
const std::vector<int> &fused_transpose_Out,
const std::string &fuse_activation,
const float fuse_alpha,
const float fuse_beta,
const float fused_output_scale,
const float scale_x,
const float scale_y,
const float scale_in_eltwise,
const float scale_out,
const bool force_fp32_output,
DenseTensor *out) {
FusedMatmulOneDNNHandler<T, T, T_out> handler(dev_ctx,
residual_data,
x_dims,
y_dims,
trans_x,
trans_y,
matmul_alpha,
x_strides_override,
y_strides_override,
is_output_fused,
fuse_activation,
fuse_alpha,
fuse_beta,
fused_output_scale,
scale_x,
scale_y,
scale_in_eltwise,
scale_out,
force_fp32_output);
const auto src_memory_p = handler.AcquireSrcMemory(&x);
const auto weights_memory_p = handler.AcquireWeightsMemory(&y);
const auto dst_memory_p = handler.AcquireDstMemory(dev_ctx, out);
auto matmul_p = handler.AcquireForwardPrimitive();
std::unordered_map<int, memory> matmul_args = {
{DNNL_ARG_SRC, *src_memory_p},
{DNNL_ARG_WEIGHTS, *weights_memory_p},
{DNNL_ARG_DST, *dst_memory_p}};
if (residual_data) {
auto residual_data_vec = vectorize(residual_data->dims());
std::shared_ptr<dnnl::memory> residual_data_memory_p;
if (std::max((x_dims)[0], (y_dims)[0]) > 1 &&
residual_data_vec.size() == 4 && residual_data_vec[0] == 1 &&
residual_data_vec[1] > 1 && residual_data_vec[2] > 1 &&
residual_data_vec[3] > 1) {
residual_data_memory_p = handler.AcquireSrcMemoryStride(residual_data);
} else {
residual_data_memory_p = handler.AcquireSrcMemoryResidual(residual_data);
}
matmul_args.insert({DNNL_ARG_ATTR_MULTIPLE_POST_OP(0) | DNNL_ARG_SRC_1,
*residual_data_memory_p});
}
if (scale_x != 1.0f) {
dnnl::memory::desc src_scales_md(
{1}, dnnl::memory::data_type::f32, dnnl::memory::format_tag::x);
auto src_scales_mem =
std::make_shared<dnnl::memory>(src_scales_md, dev_ctx.GetEngine());
*reinterpret_cast<float *>(src_scales_mem->get_data_handle()) =
1.f / scale_x;
matmul_args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_SRC, *src_scales_mem});
}
if (scale_y != 1.0f || matmul_alpha != 1.0f) {
dnnl::memory::desc wei_scales_md(
{1}, dnnl::memory::data_type::f32, dnnl::memory::format_tag::x);
auto wei_scales_mem =
std::make_shared<dnnl::memory>(wei_scales_md, dev_ctx.GetEngine());
*reinterpret_cast<float *>(wei_scales_mem->get_data_handle()) =
matmul_alpha / scale_y;
matmul_args.insert(
{DNNL_ARG_ATTR_SCALES | DNNL_ARG_WEIGHTS, *wei_scales_mem});
}
if (!force_fp32_output && scale_out != 1.0f) {
dnnl::memory::desc dst_scales_md(
{1}, dnnl::memory::data_type::f32, dnnl::memory::format_tag::x);
auto dst_scales_mem =
std::make_shared<dnnl::memory>(dst_scales_md, dev_ctx.GetEngine());
*reinterpret_cast<float *>(dst_scales_mem->get_data_handle()) =
1.f / scale_out;
matmul_args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_DST, *dst_scales_mem});
}
auto &astream = OneDNNContext::tls().get_stream();
matmul_p->execute(astream, matmul_args);
astream.wait();
if (is_output_fused && !funcs::is_int8<T_out>()) {
auto permuted_md =
dst_memory_p->get_desc().permute_axes(fused_transpose_Out);
out->set_mem_desc(permuted_md.reshape(vectorize<int64_t>(out->dims())));
} else {
out->set_mem_desc(
dst_memory_p->get_desc().reshape(vectorize<int64_t>(out->dims())));
}
}
std::vector<int64_t> GetInputShape(DDim input_dims,
std::vector<int> shape,
std::vector<int> axis) {
if (!shape.empty() && !axis.empty()) {
return vectorize(input_dims.reshape(shape).transpose(axis));
}
return vectorize(input_dims);
}
void CalculateMatrixDims(const std::vector<int64_t> &x_dims,
const std::vector<int64_t> &y_dims,
std::vector<int64_t> *x_bd_dims,
std::vector<int64_t> *y_bd_dims,
DenseTensor *out,
const bool is_output_fused) {
if (x_dims.size() == 1) {
(*x_bd_dims)[(*x_bd_dims).size() - 1] = x_dims[0];
} else if (x_dims.size() == 2) {
(*x_bd_dims)[(*x_bd_dims).size() - 1] = x_dims[1];
(*x_bd_dims)[(*x_bd_dims).size() - 2] = x_dims[0];
} else {
for (size_t i = 0; i < x_dims.size(); ++i) {
(*x_bd_dims)[(*x_bd_dims).size() - x_dims.size() + i] = x_dims[i];
}
}
if (y_dims.size() == 1) {
(*y_bd_dims)[(*x_bd_dims).size() - 2] = y_dims[0];
} else if (y_dims.size() == 2) {
(*y_bd_dims)[(*y_bd_dims).size() - 1] = y_dims[1];
(*y_bd_dims)[(*y_bd_dims).size() - 2] = y_dims[0];
} else {
for (size_t i = 0; i < y_dims.size(); ++i) {
(*y_bd_dims)[(*y_bd_dims).size() - y_dims.size() + i] = y_dims[i];
}
}
if (!is_output_fused && x_dims.size() > 2 && y_dims.size() > 2) {
auto out_dims = vectorize(out->dims());
for (size_t i = 0; i < (*x_bd_dims).size() - 2; ++i) {
PADDLE_ENFORCE_EQ(
(*x_bd_dims)[i] == (*y_bd_dims)[i] || (*x_bd_dims)[i] == 1 ||
(*y_bd_dims)[i] == 1,
true,
errors::InvalidArgument(
"Tensor dimensions are incorrect for broadcasting."
"Dimensions in X and Y must be same or equal to 1, but "
"received x_dim[%d]=%d and y_dims[%d]= %d",
i,
(*x_bd_dims)[i],
i,
(*y_bd_dims)[i]));
(out_dims)[i] = std::max((*x_bd_dims)[i], (*y_bd_dims)[i]);
}
out->Resize((out_dims));
}
}
template <typename T, typename Context>
void FusedMatmulKernel(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
const optional<DenseTensor> &residual_data,
bool transpose_x,
bool transpose_y,
const float matmul_alpha,
const std::string &fuse_activation,
const float fuse_alpha,
const float fuse_beta,
const float fused_output_scale,
const std::vector<int> &fused_reshape_X,
const std::vector<int> &fused_transpose_X,
const std::vector<int> &fused_reshape_Y,
const std::vector<int> &fused_transpose_Y,
const std::vector<int> &fused_reshape_Out,
const std::vector<int> &fused_transpose_Out,
const std::string &mkldnn_data_type UNUSED,
const float scale_x,
const float scale_y,
const float scale_in_eltwise,
const float scale_out,
const bool force_fp32_output,
DenseTensor *out) {
if (dev_ctx.HasDnnAttr("head_number")) {
const auto head_number =
PADDLE_GET_CONST(int, dev_ctx.GetDnnAttr("head_number"));
PADDLE_ENFORCE_EQ(
head_number,
1,
errors::Unimplemented(
"oneDNN matmul doesn't support multiple heads. Expected "
"head_number=1. But received `head_number` is %d",
head_number));
}
constexpr bool is_int8 = funcs::is_int8<T>();
constexpr bool is_bfloat16 = funcs::is_bfloat16<T>();
bool fuse_relu = false;
if (fuse_activation == "relu" || fuse_activation == "relu6") {
fuse_relu = true;
}
auto x_dims = GetInputShape(x.dims(), fused_reshape_X, fused_transpose_X);
auto y_dims = GetInputShape(y.dims(), fused_reshape_Y, fused_transpose_Y);
auto is_output_fused =
!fused_reshape_Out.empty() && !fused_transpose_Out.empty();
auto x_strides_override = funcs::GetInputStrides(
"X", x.dims(), transpose_x, fused_reshape_X, fused_transpose_X);
auto y_strides_override = funcs::GetInputStrides(
"Y", y.dims(), transpose_y, fused_reshape_Y, fused_transpose_Y);
int ndims = static_cast<int>(std::max(x_dims.size(), y_dims.size()));
ndims = std::max(ndims, 3);
std::vector<int64_t> x_bd_dims(ndims, 1);
std::vector<int64_t> y_bd_dims(ndims, 1);
CalculateMatrixDims(
x_dims, y_dims, &x_bd_dims, &y_bd_dims, out, is_output_fused);
if (force_fp32_output || ((!is_int8) && (!is_bfloat16))) {
ExecuteFusedMatmul<T, float>(dev_ctx,
x,
y,
residual_data.get_ptr(),
x_bd_dims,
y_bd_dims,
transpose_x,
transpose_y,
matmul_alpha,
x_strides_override,
y_strides_override,
is_output_fused,
fused_transpose_Out,
fuse_activation,
fuse_alpha,
fuse_beta,
fused_output_scale,
scale_x,
scale_y,
scale_in_eltwise,
scale_out,
force_fp32_output,
out);
} else if (is_bfloat16) {
ExecuteFusedMatmul<T, phi::bfloat16>(dev_ctx,
x,
y,
residual_data.get_ptr(),
x_bd_dims,
y_bd_dims,
transpose_x,
transpose_y,
matmul_alpha,
x_strides_override,
y_strides_override,
is_output_fused,
fused_transpose_Out,
fuse_activation,
fuse_alpha,
fuse_beta,
fused_output_scale,
scale_x,
scale_y,
scale_in_eltwise,
scale_out,
force_fp32_output,
out);
} else if (fuse_relu) {
ExecuteFusedMatmul<T, uint8_t>(dev_ctx,
x,
y,
residual_data.get_ptr(),
x_bd_dims,
y_bd_dims,
transpose_x,
transpose_y,
matmul_alpha,
x_strides_override,
y_strides_override,
is_output_fused,
fused_transpose_Out,
fuse_activation,
fuse_alpha,
fuse_beta,
fused_output_scale,
scale_x,
scale_y,
scale_in_eltwise,
scale_out,
force_fp32_output,
out);
} else {
ExecuteFusedMatmul<T, int8_t>(dev_ctx,
x,
y,
residual_data.get_ptr(),
x_bd_dims,
y_bd_dims,
transpose_x,
transpose_y,
matmul_alpha,
x_strides_override,
y_strides_override,
is_output_fused,
fused_transpose_Out,
fuse_activation,
fuse_alpha,
fuse_beta,
fused_output_scale,
scale_x,
scale_y,
scale_in_eltwise,
scale_out,
force_fp32_output,
out);
}
}
} // namespace fusion
} // namespace phi
PD_REGISTER_KERNEL(fused_matmul,
OneDNN,
ONEDNN,
phi::fusion::FusedMatmulKernel,
float,
phi::bfloat16,
int8_t,
uint8_t) {
kernel->OutputAt(0).SetDataType(phi::DataType::UNDEFINED);
}