// 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 #include #include #include #include #include #include #include #include #include #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 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(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 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(qBuffer) + blocksize * 3 * i; const T* k_src_each_batch = reinterpret_cast(kBuffer) + blocksize * 3 * i; const T* v_src_each_batch = reinterpret_cast(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(q_dst_each_line, q_src_each_line, cols_per_bmHead); arraycpy(k_dst_each_line, k_src_each_line, cols_per_bmHead); arraycpy(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 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(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 ta = {"N"}; std::array 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::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::lowest(); max[tid][ii] = std::numeric_limits::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 void SelfDPAttenKernel(const Context& dev_ctx, const DenseTensor& x, const float alpha, const int head_number, DenseTensor* out) { auto* input_d = x.data(); auto* output_d = dev_ctx.template Alloc(out); float scale = static_cast(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(input_dims[0]); int seq_len = static_cast(input_dims[1]); int head_size = static_cast(input_dims[4]); DenseTensor temp1, temp2; temp1.Resize(input_dims); float* trans_input = dev_ctx.template Alloc(&temp1); temp2.Resize(input_dims); float* trans_output = dev_ctx.template Alloc(&temp2); transpose_before_bmm1( 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( 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) {}