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

604 lines
22 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/kernels/matmul_kernel.h"
#include "paddle/phi/backends/onednn/matmul_utils.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/scale_kernel.h"
using dnnl::engine;
using dnnl::inner_product_forward;
using dnnl::memory;
using dnnl::prop_kind;
namespace phi {
KernelKey MatmulGetkernelTypeForVar(const GetKernelTypeForVarContext *ctx) {
const DenseTensor &tensor = ctx->GetTensor();
const KernelKey &expected_kernel_type = ctx->GetKernelKey();
if (phi::IsComplexType(expected_kernel_type.dtype())) {
// only promote inputs's types when contains complex input
return phi::KernelKey(tensor.place(), tensor.layout(), tensor.dtype());
} else {
#ifdef PADDLE_WITH_DNNL
// When matmul_v2 is first oneDNN op in a chain (there was some non oneDNN
// op previously) then we also need to rotate shape NHWC -> NCWH
if ((expected_kernel_type.layout() == DataLayout::ONEDNN) &&
(tensor.layout() != DataLayout::ONEDNN) &&
OneDNNContext::tls().get_cur_paddle_data_layout() == DataLayout::NHWC) {
return phi::KernelKey(
tensor.place(), DataLayout::NHWC, expected_kernel_type.dtype());
}
#endif
return phi::KernelKey(
tensor.place(), tensor.layout(), expected_kernel_type.dtype());
}
}
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) {
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 (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 MatmulKernel(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
bool transpose_x,
bool transpose_y,
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>();
const bool force_fp32_output =
dev_ctx.HasDnnAttr("force_fp32_output")
? PADDLE_GET_CONST(bool, dev_ctx.GetDnnAttr("force_fp32_output"))
: false;
auto x_dims = vectorize(x.dims());
auto y_dims = vectorize(y.dims());
int ndims = std::max(x_dims.size(), y_dims.size()); // NOLINT
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);
if (force_fp32_output || ((!is_int8) && (!is_bfloat16))) {
funcs::ExecuteMatmul<T, float>(
dev_ctx, x, y, x_bd_dims, y_bd_dims, transpose_x, transpose_y, out);
} else if (is_bfloat16) {
funcs::ExecuteMatmul<T, phi::bfloat16>(
dev_ctx, x, y, x_bd_dims, y_bd_dims, transpose_x, transpose_y, out);
} else {
funcs::ExecuteMatmul<T, int8_t>(
dev_ctx, x, y, x_bd_dims, y_bd_dims, transpose_x, transpose_y, out);
}
}
template <typename XT, typename YT, typename OT>
class MulPrimitiveFactory {
public:
explicit MulPrimitiveFactory(const engine &engine) : engine_(engine) {}
inner_product_forward CreateMulPrimitive(const DenseTensor *x_input,
const DenseTensor *y_input,
DenseTensor *output,
int x_num_col_dims,
int y_num_col_dims,
const OneDNNContext &dev_ctx) {
// TODO(intel-minghui) : Remove the restriction that only supports Input(Y)
// as weights
PADDLE_ENFORCE_EQ(
(std::is_same<YT, float>::value),
true,
errors::InvalidArgument(
"Input(Y) must be fp32 data type since only fp32 data type is "
"supported in the current design of OneDNN INT8."));
/* check data format and reorder if need */
auto x_matrix = UpdateDataFormat<XT>(x_input, x_num_col_dims, dev_ctx);
auto y_matrix = UpdateDataFormat<YT>(y_input, y_num_col_dims, dev_ctx);
auto output_dim = output->dims();
if (output_dim.size() != 2) {
output->Resize({x_matrix.dims()[0], y_matrix.dims()[1]});
}
if (mul_) {
UpdateDataPointers(dev_ctx, output, &x_matrix);
Execute();
return *(mul_);
}
auto src_desc =
CreateMemDescriptor<XT>(&x_matrix, funcs::OneDNNMemoryFormat::nc);
x_input_ = CreateMemory<XT>(src_desc, &x_matrix);
if (is_int8_) {
const auto trans_y = TransposeInputY(&y_matrix);
auto scale_y = dev_ctx.HasDnnAttr("scale_y")
? PADDLE_GET_CONST(std::vector<float>,
dev_ctx.GetDnnAttr("scale_y"))
: std::vector<float>();
y_input_ = QuantInputY(trans_y, scale_y);
} else {
y_input_ = TransposeInputY(&y_matrix);
}
auto dst_desc =
CreateMemDescriptor<OT>(output, funcs::OneDNNMemoryFormat::any);
mul_ = CreateMulPrimitive(*x_input_, *y_input_, dst_desc, output, dev_ctx);
Execute();
return *(mul_);
}
private:
memory ReorderWithScale(const memory::desc &src_desc,
const memory::desc &dst_desc,
void *src_data,
const std::vector<float> &scale) {
auto mask = scale.size() > 1 ? 1 : 0;
dnnl::primitive_attr attr;
attr.set_scales_mask(DNNL_ARG_SRC, mask);
auto src_mem = memory(src_desc, engine_, src_data);
auto dst_mem = memory(dst_desc, engine_);
auto scales_md = dnnl::memory::desc(
{1}, dnnl::memory::data_type::f32, dnnl::memory::format_tag::x);
auto scales_mem = dnnl::memory(
scales_md, engine_, funcs::to_void_cast<float>(scale.data()));
auto reorder_pd = dnnl::reorder::primitive_desc(src_mem, dst_mem, attr);
auto reorder = dnnl::reorder(reorder_pd);
auto &astream = OneDNNContext::tls().get_stream();
{
std::unordered_map<int, dnnl::memory> reorder_args;
reorder_args.insert({DNNL_ARG_SRC, src_mem});
reorder_args.insert({DNNL_ARG_DST, dst_mem});
reorder_args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_SRC, scales_mem});
reorder.execute(astream, reorder_args);
astream.wait();
}
return dst_mem;
}
memory QuantInputY(memory input_y, const std::vector<float> &scale_y) {
auto y_dims = input_y.get_desc().get_dims();
auto user_y_desc =
CreateMemDescriptor<YT>(y_dims, funcs::OneDNNMemoryFormat::oi);
auto y_desc =
CreateMemDescriptor<int8_t>(y_dims, funcs::OneDNNMemoryFormat::oi);
return ReorderWithScale(
user_y_desc, y_desc, input_y.get_data_handle(), scale_y);
}
dnnl::primitive_attr CreateMulAttr(const OneDNNContext &dev_ctx,
bool force_fp32_output) {
dnnl::primitive_attr mul_attr;
auto scale_y_data = dev_ctx.HasDnnAttr("scale_y")
? PADDLE_GET_CONST(std::vector<float>,
dev_ctx.GetDnnAttr("scale_y"))
: std::vector<float>{1.0};
auto scale_x_data =
dev_ctx.HasDnnAttr("scale_x")
? PADDLE_GET_CONST(float, dev_ctx.GetDnnAttr("scale_x"))
: 1.0f;
auto scale_out =
dev_ctx.HasDnnAttr("scale_out")
? PADDLE_GET_CONST(float, dev_ctx.GetDnnAttr("scale_out"))
: 1.0f;
auto scale_out_data = force_fp32_output ? 1.0f : scale_out;
bool is_multi_channel = scale_y_data.size() > 1;
int count = is_multi_channel ? scale_y_data.size() : 1; // NOLINT
std::vector<float> output_shift_scale(count);
for (int i = 0; i < count; i++) {
if (scale_y_data[i] == 0.0)
output_shift_scale[i] = scale_out_data;
else
output_shift_scale[i] =
scale_out_data / (scale_x_data * scale_y_data[i]);
}
int mul_mask = is_multi_channel ? 1 : 0;
mul_attr.set_scales_mask(DNNL_ARG_WEIGHTS, mul_mask);
auto scales_md = dnnl::memory::desc(
{count}, dnnl::memory::data_type::f32, dnnl::memory::format_tag::x);
scales_mem_ = dnnl::memory(scales_md, engine_);
auto mem_buf = scales_mem_.get_data_handle();
memcpy(mem_buf, output_shift_scale.data(), count * sizeof(float));
return mul_attr;
}
inner_product_forward CreateMulPrimitive(const memory &x_memory,
const memory &y_memory,
const memory::desc &dst_desc,
DenseTensor *output,
const OneDNNContext &dev_ctx) {
const auto x_desc = x_memory.get_desc();
const auto y_desc = y_memory.get_desc();
inner_product_forward::primitive_desc mul_prim_desc;
if (is_int8_) {
bool force_fp32_output =
dev_ctx.HasDnnAttr("force_fp32_output")
? PADDLE_GET_CONST(bool, dev_ctx.GetDnnAttr("force_fp32_output"))
: false;
auto mul_attr = CreateMulAttr(dev_ctx, force_fp32_output);
mul_prim_desc = inner_product_forward::primitive_desc(
engine_, prop_kind::forward, x_desc, y_desc, dst_desc, mul_attr);
} else {
mul_prim_desc = inner_product_forward::primitive_desc(
engine_, prop_kind::forward, x_desc, y_desc, dst_desc);
}
output_ = CreateDstMemory(mul_prim_desc, dev_ctx, output);
return inner_product_forward(mul_prim_desc);
}
void Execute() {
auto &astream = OneDNNContext::tls().get_stream();
(*mul_).execute(astream,
{{DNNL_ARG_SRC, *x_input_},
{DNNL_ARG_WEIGHTS, *y_input_},
{DNNL_ARG_DST, *output_},
{DNNL_ARG_ATTR_SCALES | DNNL_ARG_WEIGHTS, scales_mem_}});
astream.wait();
}
template <typename T>
DenseTensor UpdateDataFormat(const DenseTensor *data,
int num_col_dims,
const OneDNNContext &dev_ctx) {
DenseTensor x_tmp;
DenseTensor data_matrix;
// This code is enforcing plain (non-blocked) memory arrangement
// in order to flatten (reduce dimensionality) of DenseTensor later
auto src_mdesc = data->mem_desc();
auto dst_mdesc = data->dims().size() >= 4
? (data->dims().size() == 5
? CreateMemDescriptor<T>(
data, funcs::OneDNNMemoryFormat::ncdhw)
: CreateMemDescriptor<T>(
data, funcs::OneDNNMemoryFormat::nchw))
: src_mdesc;
if (src_mdesc != dst_mdesc) {
dev_ctx.template Alloc<T>(&x_tmp, data->memory_size());
Reorder(src_mdesc,
dst_mdesc,
funcs::to_void_cast<T>(data->data<T>()),
funcs::to_void_cast<T>(x_tmp.data<T>()));
x_tmp.Resize(data->dims());
x_tmp.set_mem_desc(dst_mdesc);
data_matrix = ReshapeToMatrix(x_tmp, num_col_dims);
} else {
data_matrix = ReshapeToMatrix(*data, num_col_dims);
}
return data_matrix;
}
void UpdateDataPointers(const OneDNNContext &dev_ctx,
DenseTensor *out,
const DenseTensor *in) {
x_input_->set_data_handle(funcs::to_void_cast<XT>(in->data<XT>()));
output_->set_data_handle(dev_ctx.template Alloc<OT>(out));
out->set_mem_desc(output_->get_desc());
}
template <typename T>
memory::desc CreateMemDescriptor(
const DenseTensor *tensor,
funcs::OneDNNMemoryFormat format,
memory::data_type type = funcs::OneDNNGetDataType<T>()) {
auto dims = vectorize<int64_t>(tensor->dims());
return funcs::OneDNNMemDesc(dims, type, format);
}
template <typename T>
memory::desc CreateMemDescriptor(
const std::vector<int64_t> &dims,
funcs::OneDNNMemoryFormat format,
memory::data_type type = funcs::OneDNNGetDataType<T>()) {
return funcs::OneDNNMemDesc(dims, type, format);
}
template <typename T>
memory CreateMemory(const memory::desc &desc, const DenseTensor *tensor) {
return memory(desc, engine_, funcs::to_void_cast<T>(tensor->data<T>()));
}
memory CreateDstMemory(
const inner_product_forward::primitive_desc &mul_prim_desc,
const OneDNNContext &dev_ctx,
DenseTensor *output) {
auto dst_desc = mul_prim_desc.dst_desc();
auto buffer_size = dst_desc.get_size();
OT *output_data = dev_ctx.template Alloc<OT>(output, buffer_size);
output->set_mem_desc(dst_desc);
return memory(dst_desc, engine_, funcs::to_void_cast<OT>(output_data));
}
memory Reorder(const memory::desc &src_desc,
const memory::desc &dst_desc,
void *src_data,
void *dst_data = nullptr) {
auto src_mem = memory(src_desc, engine_, src_data);
auto dst_mem = dst_data ? memory(dst_desc, engine_, dst_data)
: memory(dst_desc, engine_);
auto reorder = dnnl::reorder(src_mem, dst_mem);
auto &astream = OneDNNContext::tls().get_stream();
{
reorder.execute(astream, src_mem, dst_mem);
astream.wait();
}
return dst_mem;
}
memory TransposeInputY(const DenseTensor *input_y) {
auto dims = vectorize<int64_t>(input_y->dims());
std::swap(dims[0], dims[1]); // Correct output dimensions
auto src_desc =
CreateMemDescriptor<YT>(dims, funcs::OneDNNMemoryFormat::io);
auto dst_desc =
CreateMemDescriptor<YT>(dims, funcs::OneDNNMemoryFormat::oi);
return Reorder(
src_desc, dst_desc, funcs::to_void_cast<YT>(input_y->data<YT>()));
}
const engine &engine_;
optional<memory> x_input_;
optional<memory> y_input_;
optional<memory> output_;
optional<inner_product_forward> mul_;
static constexpr bool is_int8_ = funcs::is_int8<XT>();
dnnl::memory scales_mem_;
};
/* OT: output data type */
template <typename XT, typename YT, typename OT>
std::shared_ptr<MulPrimitiveFactory<XT, YT, OT>> GetPrimitiveFactory(
const OneDNNContext &dev_ctx,
const DenseTensor *input_x,
const DenseTensor *input_y,
const engine &onednn_engine) {
std::string key = funcs::CreateKey(dev_ctx,
TransToProtoVarType(input_x->dtype()),
vectorize(input_x->dims()),
TransToProtoVarType(input_y->dtype()),
vectorize(input_y->dims()),
dev_ctx.GetOutputsName("Out")[0]);
key = funcs::ExtendKeyWithThreadInfoIfNeeded(dev_ctx, key);
auto prim_creator = std::static_pointer_cast<MulPrimitiveFactory<XT, YT, OT>>(
dev_ctx.GetBlob(key));
if (prim_creator == nullptr) {
prim_creator =
std::make_shared<MulPrimitiveFactory<XT, YT, OT>>(onednn_engine);
dev_ctx.SetBlob(key, prim_creator);
}
return prim_creator;
}
/* XT: input x data type, YT: input y data type */
template <typename XT, typename YT>
inner_product_forward GetMulPrimitive(const OneDNNContext &dev_ctx,
const DenseTensor *input_x,
const DenseTensor *input_y,
DenseTensor *output,
int x_num_col_dims,
int y_num_col_dims,
const engine &onednn_engine) {
constexpr bool is_int8 = funcs::is_int8<XT>();
bool force_fp32_output =
dev_ctx.HasDnnAttr("force_fp32_output")
? PADDLE_GET_CONST(bool, dev_ctx.GetDnnAttr("force_fp32_output"))
: false;
if (is_int8 && !force_fp32_output) {
return GetPrimitiveFactory<XT, YT, int8_t>(
dev_ctx, input_x, input_y, onednn_engine)
->CreateMulPrimitive(
input_x, input_y, output, x_num_col_dims, y_num_col_dims, dev_ctx);
} else {
return GetPrimitiveFactory<XT, YT, float>(
dev_ctx, input_x, input_y, onednn_engine)
->CreateMulPrimitive(
input_x, input_y, output, x_num_col_dims, y_num_col_dims, dev_ctx);
}
}
/* XT: input x data type */
template <typename XT, typename Context>
void MatmulWithFlattenKernelINT8(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
int x_num_col_dims,
int y_num_col_dims,
DenseTensor *out) {
OneDNNContext::tls().log_lib_version();
auto &onednn_engine = dev_ctx.GetEngine();
auto out_dims = out->dims();
auto mul = GetMulPrimitive<XT, float>(
dev_ctx, &x, &y, out, x_num_col_dims, y_num_col_dims, onednn_engine);
if (out_dims.size() != 2) {
out->Resize(out_dims);
}
auto in_md = dnnl_primitive_desc_query_md(
mul.get_primitive_desc(), dnnl_query_dst_md, 0);
dnnl_memory_desc_t cloned_in_md = nullptr;
dnnl_memory_desc_clone(&cloned_in_md, in_md);
out->set_mem_desc(
memory::desc(cloned_in_md).reshape(vectorize<int64_t>(out->dims())));
}
template <typename T, typename Context>
void MatmulWithFlattenKernel(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
int x_num_col_dims,
int y_num_col_dims,
DenseTensor *out) {
constexpr bool is_int8 = funcs::is_int8<T>();
if (is_int8) {
MatmulWithFlattenKernelINT8<T, Context>(
dev_ctx, x, y, x_num_col_dims, y_num_col_dims, out);
return;
}
const DenseTensor x_matrix =
x.dims().size() > 2 ? ReshapeToMatrix(x, x_num_col_dims) : x;
const DenseTensor y_matrix =
y.dims().size() > 2 ? ReshapeToMatrix(y, y_num_col_dims) : y;
// adding mb dim because MatMulV2 handler needs it
std::vector<int64_t> x_dims(3, 1);
std::vector<int64_t> y_dims(3, 1);
x_dims[1] = x_matrix.dims()[0];
x_dims[2] = x_matrix.dims()[1];
y_dims[1] = y_matrix.dims()[0];
y_dims[2] = y_matrix.dims()[1];
funcs::ExecuteMul<T>(
dev_ctx, x_matrix, y_matrix, x_dims, y_dims, false, false, out);
}
template <typename T, typename Context>
void LegacyMatmulKernel(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &y,
bool transpose_x,
bool transpose_y,
float alpha,
DenseTensor *out) {
MatmulKernel<T, Context>(dev_ctx, x, y, transpose_x, transpose_y, out);
if (std::fabs(alpha - 1.f) > 1e-6f) {
ScaleKernel<T, Context>(
dev_ctx, *out, Scalar(alpha), Scalar(0), false, out);
}
}
} // namespace phi
PD_REGISTER_KERNEL(matmul,
OneDNN,
ONEDNN,
phi::MatmulKernel,
float,
phi::bfloat16,
int8_t,
uint8_t) {
kernel->get_kerneltype_forvar_fn_ = phi::MatmulGetkernelTypeForVar;
}
PD_REGISTER_KERNEL(matmul_with_flatten,
OneDNN,
ONEDNN,
phi::MatmulWithFlattenKernel,
float,
phi::bfloat16,
uint8_t,
int8_t) {}
PD_REGISTER_KERNEL(legacy_matmul,
OneDNN,
ONEDNN,
phi::LegacyMatmulKernel,
float,
phi::bfloat16) {
kernel->get_kerneltype_forvar_fn_ = phi::MatmulGetkernelTypeForVar;
}