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

519 lines
17 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 <immintrin.h>
#include <omp.h>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <new>
#include <string>
#include "glog/logging.h"
#ifdef PADDLE_WITH_DNNL
#include "dnnl.hpp" //NOLINT
#endif
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/core/tensor_utils.h"
namespace phi {
namespace fusion {
template <typename T, typename Tt>
void arraycpy(T* dst, const Tt* src, int n) {
#ifdef PADDLE_WITH_MKLML
#pragma omp simd
#endif
for (int i = 0; i < n; i++) {
dst[i] = static_cast<T>(src[i]);
}
}
// batches x tokens x 3 x head x heads -> 3 x batches x head x tokens x heads
// (2 0 3 1 4)
template <typename T, typename Tt>
void transpose_before_bmm1(const T* qkvBuffer,
Tt* qkvTransBuffer,
int batchSize,
int tokenSize,
int headNum,
int headSize) {
int hiddenSize = headNum * headSize;
int blocksize = tokenSize * hiddenSize; // dst buffer stride in each batch
const T* qBuffer = qkvBuffer;
const T* kBuffer = qkvBuffer + hiddenSize;
const T* vBuffer = qkvBuffer + hiddenSize * 2;
Tt* q_buffer = qkvTransBuffer;
Tt* k_buffer = qkvTransBuffer + batchSize * blocksize;
Tt* v_buffer = qkvTransBuffer + batchSize * blocksize * 2;
int bmHead = headNum;
int cols_per_bmHead = hiddenSize / headNum; // 768/12 = 64
#ifdef PADDLE_WITH_MKLML
#pragma omp parallel for collapse(3)
#endif
for (int i = 0; i < batchSize; i++) {
for (int k = 0; k < bmHead; k++) {
for (int j = 0; j < tokenSize; j++) {
const T* q_src_each_batch =
reinterpret_cast<const T*>(qBuffer) + blocksize * 3 * i;
const T* k_src_each_batch =
reinterpret_cast<const T*>(kBuffer) + blocksize * 3 * i;
const T* v_src_each_batch =
reinterpret_cast<const T*>(vBuffer) + blocksize * 3 * i;
int dst_offset_each_bmHead = k * tokenSize * cols_per_bmHead;
int src_offset_each_line = k * cols_per_bmHead;
int dst_offset_each_line = j * cols_per_bmHead;
int src_offset_each_bmHead = j * hiddenSize * 3;
Tt* q_dst_each_line = q_buffer + i * blocksize +
dst_offset_each_bmHead + dst_offset_each_line;
const T* q_src_each_line =
q_src_each_batch + src_offset_each_bmHead + src_offset_each_line;
Tt* k_dst_each_line = k_buffer + i * blocksize +
dst_offset_each_bmHead + dst_offset_each_line;
const T* k_src_each_line =
k_src_each_batch + src_offset_each_bmHead + src_offset_each_line;
Tt* v_dst_each_line = v_buffer + i * blocksize +
dst_offset_each_bmHead + dst_offset_each_line;
const T* v_src_each_line =
v_src_each_batch + src_offset_each_bmHead + src_offset_each_line;
arraycpy<Tt, T>(q_dst_each_line, q_src_each_line, cols_per_bmHead);
arraycpy<Tt, T>(k_dst_each_line, k_src_each_line, cols_per_bmHead);
arraycpy<Tt, T>(v_dst_each_line, v_src_each_line, cols_per_bmHead);
}
}
}
}
// batches x head x tokens x heads -> batches x tokens x head x heads (0 2 1 3)
template <typename T, typename Tt>
void transpose_after_bmm2(T* Buffer,
Tt* TransBuffer,
int batchSize,
int tokenSize,
int headNum,
int headSize) {
int hiddenSize = headNum * headSize;
int blocksize = tokenSize * hiddenSize; // dst buffer stride in each batch
int bmHead = headNum;
int cols_per_bmHead = hiddenSize / headNum; // 768/12 = 64
#ifdef PADDLE_WITH_MKLML
#pragma omp parallel for collapse(2)
#endif
for (int i = 0; i < batchSize; i++) {
for (int k = 0; k < tokenSize; k++) {
int src_offset_each_head = k * cols_per_bmHead;
int dst_offset_each_line = k * hiddenSize;
for (int j = 0; j < bmHead; j++) {
int src_offset_each_line = j * tokenSize * cols_per_bmHead;
int dst_offset_each_head = j * cols_per_bmHead;
Tt* q_dst_each_line = TransBuffer + dst_offset_each_head +
dst_offset_each_line + i * blocksize;
const T* q_src_each_line = Buffer + src_offset_each_line +
src_offset_each_head + i * blocksize;
arraycpy<Tt, T>(q_dst_each_line, q_src_each_line, cols_per_bmHead);
}
}
}
}
// C = A * B
// bTranspose: B need to be transposed or not
void sgemm(const float* A,
const float* B,
float* C,
int m,
int n,
int k,
bool transa,
bool transb) {
#ifdef PADDLE_WITH_DNNL
int lda = (transa ? m : k);
int ldb = (transb ? k : n);
int ldc = n;
float alpha = 1;
float beta = 0;
std::array<char, 2> ta = {"N"};
std::array<char, 2> tb = {"N"};
if (transa) ta[0] = 'T';
if (transb) tb[0] = 'T';
dnnl_sgemm(ta[0], tb[0], m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);
#else
LOG(ERROR) << "scaled_dp_atten not supported without WITH_MKL!";
#endif
}
// exp based-on jit code
static inline __m512 vexp(const __m512& _x) {
__m512 p16f_1 = _mm512_set1_ps(1.0f);
__m512 p16f_half = _mm512_set1_ps(0.5f);
__m512 p16f_127 = _mm512_set1_ps(127.f);
__m512 p16f_exp_hi = _mm512_set1_ps(88.3762626647950f);
__m512 p16f_exp_lo = _mm512_set1_ps(-88.3762626647949f);
__m512 p16f_cephes_LOG2EF = _mm512_set1_ps(1.44269504088896341f);
__m512 p16f_cephes_exp_p0 = _mm512_set1_ps(1.9875691500E-4f);
__m512 p16f_cephes_exp_p1 = _mm512_set1_ps(1.3981999507E-3f);
__m512 p16f_cephes_exp_p2 = _mm512_set1_ps(8.3334519073E-3f);
__m512 p16f_cephes_exp_p3 = _mm512_set1_ps(4.1665795894E-2f);
__m512 p16f_cephes_exp_p4 = _mm512_set1_ps(1.6666665459E-1f);
__m512 p16f_cephes_exp_p5 = _mm512_set1_ps(5.0000001201E-1f);
// Clamp x.
__m512 x = _mm512_max_ps(_mm512_min_ps(_x, p16f_exp_hi), p16f_exp_lo);
// Express exp(x) as exp(m*ln(2) + r), start by extracting
// m = floor(x/ln(2) + 0.5).
__m512 m = _mm512_floor_ps(_mm512_fmadd_ps(x, p16f_cephes_LOG2EF, p16f_half));
// Get r = x - m*ln(2).
__m512 p16f_nln2 = _mm512_set1_ps(-0.6931471805599453f);
__m512 r = _mm512_fmadd_ps(m, p16f_nln2, x);
__m512 r2 = _mm512_mul_ps(r, r);
__m512 y = p16f_cephes_exp_p0;
y = _mm512_fmadd_ps(y, r, p16f_cephes_exp_p1);
y = _mm512_fmadd_ps(y, r, p16f_cephes_exp_p2);
y = _mm512_fmadd_ps(y, r, p16f_cephes_exp_p3);
y = _mm512_fmadd_ps(y, r, p16f_cephes_exp_p4);
y = _mm512_fmadd_ps(y, r, p16f_cephes_exp_p5);
y = _mm512_fmadd_ps(y, r2, r);
y = _mm512_add_ps(y, p16f_1);
// Build emm0 = 2^m.
__m512i emm0 = _mm512_cvttps_epi32(_mm512_add_ps(m, p16f_127));
emm0 = _mm512_slli_epi32(emm0, 23);
// Return 2^m * exp(r).
return _mm512_max_ps(_mm512_mul_ps(y, _mm512_castsi512_ps(emm0)), _x);
}
// need to do for res.
void softmax_sum_max(float* AB,
float* sum,
float* max,
float* pre_sum,
float* pre_max,
float refac,
int m,
int k) {
float max_val = std::numeric_limits<float>::lowest();
__m512 vrefac = _mm512_set1_ps(refac);
for (int i = 0; i < m; ++i) {
float* buf = AB + i * k;
// max val for avoiding inf and nan
__m512 vmax = _mm512_set1_ps(max_val);
for (int off = 0; off < k; off += 16) {
int remain = k - off;
__mmask16 mask = (remain >= 16 ? 0xffff : (1 << remain) - 1);
__m512 vx = _mm512_maskz_loadu_ps(mask, buf + off);
vmax = _mm512_mask_max_ps(vmax, mask, vmax, vx);
}
float _max = _mm512_reduce_max_ps(vmax);
_max *= refac;
_max = _max > max[i] ? _max : max[i];
__m512 merr = _mm512_set1_ps(max[i] - _max);
merr = vexp(merr);
max[i] = _max;
// exp and get sum
__m512 vsum = _mm512_set1_ps(0);
vmax = _mm512_set1_ps(_max);
for (int off = 0; off < k; off += 16) {
int remain = k - off;
__mmask16 mask = (remain >= 16 ? 0xffff : (1 << remain) - 1);
__m512 vx = _mm512_maskz_loadu_ps(mask, buf + off);
vx = _mm512_mask_mul_ps(vx, mask, vx, vrefac);
vx = _mm512_mask_sub_ps(vx, mask, vx, vmax);
vx = vexp(vx);
_mm512_mask_storeu_ps(buf + off, mask, vx);
vsum = _mm512_mask_add_ps(vsum, mask, vsum, vx);
}
float _sum = _mm512_reduce_add_ps(vsum);
float fac = _mm512_cvtss_f32(merr);
sum[i] = sum[i] * fac + _sum;
_sum = sum[i];
// Compute exp/sum(exp) and store
__m512 vrsum = _mm512_set1_ps(1.0f / _sum);
for (int off = 0; off < k; off += 16) {
int remain = k - off;
__mmask16 mask = (remain >= 16 ? 0xffff : (1 << remain) - 1);
__m512 vx = _mm512_maskz_loadu_ps(mask, buf + off);
vx = _mm512_mask_mul_ps(vx, mask, vx, vrsum);
_mm512_mask_storeu_ps(buf + off, mask, vx);
}
}
}
void update_out_blk(float* output,
const float* exp_ABC,
float* pre_sum,
float* sum,
float* pre_max,
float* max,
int m,
int n) {
for (int i = 0; i < m; ++i) {
const float* buf = exp_ABC + i * n;
float* outbuf = output + i * n;
__m512 merr = _mm512_set1_ps(pre_max[i] - max[i]);
merr = vexp(merr);
__m512 vfac = _mm512_set1_ps(pre_sum[i] / sum[i]);
for (int off = 0; off < n; off += 16) {
int remain = n - off;
__mmask16 mask = (remain >= 16 ? 0xffff : (1 << remain) - 1);
__m512 vout = _mm512_maskz_loadu_ps(mask, outbuf + off);
__m512 vabc = _mm512_maskz_loadu_ps(mask, buf + off);
vout = _mm512_mask_mul_ps(vout, mask, vout, merr);
vout = _mm512_mask_mul_ps(vout, mask, vout, vfac);
__m512 vupt = _mm512_set1_ps(0.0f);
vupt = _mm512_mask_add_ps(vupt, mask, vout, vabc);
_mm512_mask_storeu_ps(outbuf + off, mask, vupt);
}
pre_sum[i] = sum[i];
pre_max[i] = max[i];
}
}
// hard code: axis = 1
// sum += sum(exp(A[i]))
// output = output * pre_sum / sum + (exp(A) / sum) x B
// pre_sum = sum
void incremental_tile_attention(const float* A,
const float* B,
const float* C,
int m,
int n,
int k,
float* pre_sum,
float* sum,
float* pre_max,
float* max,
float refac,
float* AB,
float* exp_ABC,
float* output) {
sgemm(A, B, AB, m, k, n, false, true);
softmax_sum_max(AB, sum, max, pre_sum, pre_max, refac, m, k);
sgemm(AB, C, exp_ABC, m, n, k, false, false);
update_out_blk(output, exp_ABC, pre_sum, sum, pre_max, max, m, n);
}
// scaled dot-product attention: bmm1 + softmax + bmm2
void scaled_dp_attention(const float* query,
const float* key,
const float* value,
float scale,
int batch_size,
int itsize,
int otsize,
int num_head,
int head_size,
float* output) {
// output = trans(softmax(query * trans(key)) * value)
int iblk = std::min(512, itsize / 1);
int oblk = std::min(512, otsize / 1);
float refac = scale;
#ifdef PADDLE_WITH_MKLML
int nth = omp_get_max_threads();
#else
int nth = 1;
#endif
float** pre_sum;
float** sum;
float** pre_max;
float** max;
float** qk_arr;
float** exp_qkv_arr;
pre_sum = new float*[nth];
sum = new float*[nth];
pre_max = new float*[nth];
max = new float*[nth];
qk_arr = new float*[nth];
exp_qkv_arr = new float*[nth];
for (int i = 0; i < nth; ++i) {
pre_sum[i] = new float[iblk];
sum[i] = new float[iblk];
pre_max[i] = new float[iblk];
max[i] = new float[iblk];
qk_arr[i] = new float[iblk * oblk];
exp_qkv_arr[i] = new float[iblk * head_size];
}
#ifdef PADDLE_WITH_MKLML
#pragma omp parallel for collapse(3)
#endif
for (int i = 0; i < batch_size; ++i) {
for (int j = 0; j < num_head; ++j) {
for (int m = 0; m < itsize; m += iblk) {
#ifdef PADDLE_WITH_MKLML
int tid = omp_get_thread_num();
#else
int tid = 0;
#endif
int ooffset =
i * num_head * otsize * head_size + j * otsize * head_size;
const float* k = key + ooffset;
const float* v = value + ooffset;
int q_rblk = std::min(iblk, itsize - m);
int ioffset =
i * num_head * otsize * head_size + j * otsize * head_size;
const float* q = query + ioffset + m * head_size;
float* out = output + ioffset + m * head_size;
// reset out
for (int ii = 0; ii < q_rblk; ++ii) {
#ifdef PADDLE_WITH_MKLML
#pragma omp simd
#endif
for (int jj = 0; jj < head_size; ++jj) {
out[ii * head_size + jj] = 0; // reset output
}
}
// reset sum
#ifdef PADDLE_WITH_MKLML
#pragma omp simd
#endif
for (int ii = 0; ii < q_rblk; ++ii) {
pre_sum[tid][ii] = 0;
sum[tid][ii] = 0;
pre_max[tid][ii] = std::numeric_limits<float>::lowest();
max[tid][ii] = std::numeric_limits<float>::lowest();
}
//
for (int b = 0; b < otsize; b += oblk) {
int kv_rblk = std::min(oblk, otsize - b);
const float* blk_k = k + b * head_size;
const float* blk_v = v + b * head_size;
incremental_tile_attention(q,
blk_k,
blk_v,
q_rblk,
head_size,
kv_rblk,
pre_sum[tid],
sum[tid],
pre_max[tid],
max[tid],
refac,
qk_arr[tid],
exp_qkv_arr[tid],
out);
}
}
}
}
for (int i = 0; i < nth; ++i) {
delete[] pre_sum[i];
delete[] sum[i];
delete[] pre_max[i];
delete[] max[i];
delete[] qk_arr[i];
delete[] exp_qkv_arr[i];
}
delete[] pre_sum;
delete[] sum;
delete[] pre_max;
delete[] max;
delete[] qk_arr;
delete[] exp_qkv_arr;
return;
}
template <typename T, typename Context>
void SelfDPAttenKernel(const Context& dev_ctx,
const DenseTensor& x,
const float alpha,
const int head_number,
DenseTensor* out) {
auto* input_d = x.data<T>();
auto* output_d = dev_ctx.template Alloc<T>(out);
float scale = static_cast<float>(alpha);
auto input_dims = x.dims();
// in shouble be (batch * seq * 3 * head_num * head_size)
// out shouble be (batch * seq * head_num * head_size)
int batch_size = static_cast<int>(input_dims[0]);
int seq_len = static_cast<int>(input_dims[1]);
int head_size = static_cast<int>(input_dims[4]);
DenseTensor temp1, temp2;
temp1.Resize(input_dims);
float* trans_input = dev_ctx.template Alloc<float>(&temp1);
temp2.Resize(input_dims);
float* trans_output = dev_ctx.template Alloc<float>(&temp2);
transpose_before_bmm1<T, float>(
input_d, trans_input, batch_size, seq_len, head_number, head_size);
float* query = trans_input;
float* key = trans_input + batch_size * head_number * seq_len * head_size;
float* value =
trans_input + batch_size * head_number * seq_len * head_size * 2;
scaled_dp_attention(query,
key,
value,
scale,
batch_size,
seq_len,
seq_len,
head_number,
head_size,
trans_output);
transpose_after_bmm2<float, T>(
trans_output, output_d, batch_size, seq_len, head_number, head_size);
}
} // namespace fusion
} // namespace phi
PD_REGISTER_KERNEL(self_dp_attention,
CPU,
ALL_LAYOUT,
phi::fusion::SelfDPAttenKernel,
float,
double) {}