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

216 lines
9.1 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 "paddle/phi/backends/onednn/onednn_reuse.h"
#include "paddle/phi/core/kernel_registry.h"
namespace phi::fusion {
template <typename T, dnnl::algorithm BINARY_OP>
void FusedElementwiseKernel(const OneDNNContext& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const int axis,
const std::string& fuse_activation,
const float fuse_alpha,
const float fuse_beta,
const float fused_output_scale,
const std::vector<int>& fused_unsqueeze2_axes,
const float scale_x,
const float scale_y,
const float scale_out,
DenseTensor* out) {
const auto& onednn_engine = dev_ctx.GetEngine();
dnnl::post_ops post_operations;
funcs::AppendActivation(
dev_ctx, post_operations, fuse_activation, fuse_alpha, fuse_beta);
if (fused_output_scale != 1.0) {
// linear post op's formula is `alpha * dst + beta`. Here we only want to
// scale the output not shift it, so the beta is set to 0.0f.
post_operations.append_eltwise(
dnnl::algorithm::eltwise_linear, fused_output_scale, 0.0f);
}
auto* non_const_x = &x;
auto* non_const_y = &y;
funcs::BinaryOneDNNHandler<T> handler(BINARY_OP,
axis,
onednn_engine,
dev_ctx.GetPlace(),
non_const_x,
non_const_y,
out,
scale_x,
scale_y,
scale_out,
true,
post_operations);
// oneDNN's binary is optimized for broadcasting y into x, so in other case
// we have to swap tensors to achieve optimal performance
if (x.numel() < y.numel()) {
std::swap(non_const_x, non_const_y);
}
const auto src_x_memory =
handler.swin_case ? (x.numel() == y.numel()
? handler.AcquireExtendSrcMemory(non_const_x, 0)
: handler.AcquireSrcMemory(non_const_x))
: handler.AcquireSrcMemory(non_const_x);
const auto src_y_memory =
handler.swin_case ? (x.numel() == y.numel()
? handler.AcquireSecondSrcMemory(non_const_y)
: handler.AcquireExtendSrcMemory(non_const_y, 1))
: handler.AcquireSecondSrcMemory(non_const_y);
// For Inplace src and dst should be the same memory object.
// So x should share buffer with z. But UT mechanics is testing inplace
// execution for this op not checking that x can be broadcasted to match in
// shape y tensor.
// This is wrong as when x is to be broadcasted then z(out) will match the
// shape of y which is bigger than x. Hence if x is smaller in shape than z
// and they share a buffer (of shape x) then this buffer is not big enough
// to hold result of elementwise operation.
const bool reuse_x_memory = non_const_x->numel() == out->numel() &&
non_const_x->IsSharedBufferWith(*out);
std::shared_ptr<dnnl::memory> dst_memory;
if (reuse_x_memory) {
dst_memory = src_x_memory;
// NOTE(chenfeiyu): when the output reuses memory from other tensor rather
// than allocate its own, it's still need to take care of its data type.
// Unfortunately, paddle's operator only infers the output' shape, but not
// the data type. Alloc<T> takes care of allocation and data type
// normally, but if the memory is already allocated and there is no need
// to re-allocate, it just set the data type. So this it added there to
// get the right data type.
dev_ctx.template Alloc<T>(out);
} else {
dst_memory = handler.AcquireDstMemory(out);
}
const auto binary_prim = handler.AcquireForwardPrimitive();
auto& astream = OneDNNContext::tls().get_stream();
std::unordered_map<int, dnnl::memory> args = {{DNNL_ARG_SRC_0, *src_x_memory},
{DNNL_ARG_SRC_1, *src_y_memory},
{DNNL_ARG_DST, *dst_memory}};
if (handler.Has_SRC_0_Scale()) {
args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_SRC_0,
handler.Get_SRC_0_Scale_Memory()});
}
if (handler.Has_SRC_1_Scale()) {
args.insert({DNNL_ARG_ATTR_SCALES | DNNL_ARG_SRC_1,
handler.Get_SRC_1_Scale_Memory()});
}
binary_prim->execute(astream, args);
astream.wait();
auto out_md = dst_memory->get_desc();
if (handler.use_broadcasting_hack) {
auto dims = out_md.get_dims();
dims.insert(dims.begin(), non_const_x->dims()[0]);
dims[1] /= dims[0];
out_md = out_md.reshape(dims);
}
if (fused_unsqueeze2_axes.empty()) {
out->set_mem_desc(out_md);
} else {
funcs::SetOutMemDescWithUnsqueeze2FuseSupport(
fused_unsqueeze2_axes, out, out_md);
}
}
#define DEFINE_ONEDNN_ELEMENTWISE_KERNEL(name, algorithm) \
template <typename T, typename Context> \
void name##Kernel(const Context& dev_ctx, \
const DenseTensor& x, \
const DenseTensor& y, \
const int axis, \
const std::string& fuse_activation, \
const float fuse_alpha, \
const float fuse_beta, \
const float fused_output_scale, \
const std::vector<int>& fused_unsqueeze2_axes, \
const float scale_x, \
const float scale_y, \
const float scale_out, \
DenseTensor* out) { \
FusedElementwiseKernel<T, algorithm>(dev_ctx, \
x, \
y, \
axis, \
fuse_activation, \
fuse_alpha, \
fuse_beta, \
fused_output_scale, \
fused_unsqueeze2_axes, \
scale_x, \
scale_y, \
scale_out, \
out); \
}
DEFINE_ONEDNN_ELEMENTWISE_KERNEL(FusedAdd, dnnl::algorithm::binary_add)
DEFINE_ONEDNN_ELEMENTWISE_KERNEL(FusedSubtract, dnnl::algorithm::binary_sub)
DEFINE_ONEDNN_ELEMENTWISE_KERNEL(FusedMultiply, dnnl::algorithm::binary_mul)
DEFINE_ONEDNN_ELEMENTWISE_KERNEL(FusedDivide, dnnl::algorithm::binary_div)
} // namespace phi::fusion
PD_REGISTER_KERNEL(fused_elementwise_add,
OneDNN,
ONEDNN,
phi::fusion::FusedAddKernel,
float,
phi::bfloat16,
int8_t,
uint8_t) {}
PD_REGISTER_KERNEL(fused_elementwise_sub,
OneDNN,
ONEDNN,
phi::fusion::FusedSubtractKernel,
float,
phi::bfloat16,
int8_t,
uint8_t) {}
PD_REGISTER_KERNEL(fused_elementwise_mul,
OneDNN,
ONEDNN,
phi::fusion::FusedMultiplyKernel,
float,
phi::bfloat16,
int8_t,
uint8_t) {}
PD_REGISTER_KERNEL(fused_elementwise_div,
OneDNN,
ONEDNN,
phi::fusion::FusedDivideKernel,
float,
phi::bfloat16,
int8_t,
uint8_t) {}