// Copyright (c) 2024 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. #pragma once #if defined(PADDLE_WITH_FLASHATTN) && !defined(PADDLE_WITH_HIP) #include "paddle/phi/backends/dynload/flashattn.h" #endif #include "paddle/phi/backends/gpu/gpu_info.h" #include "paddle/phi/common/memory_utils.h" #include "paddle/phi/kernels/arange_kernel.h" #include "paddle/phi/kernels/funcs/broadcast_function.h" #include "paddle/phi/kernels/funcs/elementwise_functor.h" #include "paddle/phi/kernels/funcs/reduce_function.h" #include "paddle/phi/kernels/funcs/transpose_function.cuh" #include "paddle/phi/kernels/fusion/gpu/attn_gemm.h" #include "paddle/phi/kernels/gpudnn/softmax_gpudnn.h" namespace phi { namespace funcs { template __global__ void SimpleScaleKernel(int64_t numel, float scale, T* inout) { CUDA_KERNEL_LOOP_TYPE(i, numel, int64_t) { inout[i] = static_cast(scale * static_cast(inout[i])); } } inline std::string MemoryDebugString(const DenseTensor& t) { int device_id = phi::backends::gpu::GetCurrentDeviceId(); int64_t allocated = phi::memory_utils::DeviceMemoryStatCurrentValue("Allocated", device_id); int64_t reserved = phi::memory_utils::DeviceMemoryStatCurrentValue("Reserved", device_id); std::stringstream ss; ss << "shape=[" << t.dims() << "], size=" << static_cast(t.memory_size()) / (1 << 20) << " MB, ptr=" << t.data() << "; [MEMORY] allocated=" << static_cast(allocated) / (1 << 20) << " MB" << ", reserved=" << static_cast(reserved) / (1 << 20) << " MB"; return ss.str(); } template void AllocWithDebugInfo(const GPUContext& dev_ctx, const std::string& info, DenseTensor* t) { dev_ctx.Alloc(t, t->numel() * sizeof(T)); if (VLOG_IS_ON(4)) { VLOG(4) << info << ": " << MemoryDebugString(*t); } } inline std::string TensorDebugString(const DenseTensor* t, const std::string& info) { std::stringstream ss; ss << info << ": "; if (t) { if (t->initialized()) { ss << "shape=[" << t->dims() << "], ptr=" << t->data(); } else { ss << "not initialized"; } } else { ss << "nullptr"; } return ss.str(); } inline void WaitWithDebugInfo(const GPUContext& dev_ctx) { if (VLOG_IS_ON(5)) { dev_ctx.Wait(); VLOG(5) << "[Flash attn Synchronize] "; } } template inline void TypeDebugInfo() { if (VLOG_IS_ON(4)) { if (std::is_same::value) { VLOG(4) << "[Grad]: T is phi::float16."; } else if (std::is_same::value) { VLOG(4) << "[Grad]: T is phi::bfloat16."; } else if (std::is_same::value) { VLOG(4) << "[Grad]: T is float."; } } } template struct TernaryAddFunctor { inline HOSTDEVICE T operator()(T a, T b, T c) const { return a + b + c; } }; template struct GateAttentionConfig { public: const GPUContext& dev_ctx; bool merge_qkv; bool has_gating; bool use_flash_attn; int64_t batch_size; int64_t seq_len_m; int64_t seq_len_r; int64_t q_dim; int64_t kv_dim; int64_t head_dim; int64_t m_size; int64_t num_heads; DDim qkv_out_dims; DDim qkv_transpose_out_dims; DDim q_out_dims; DDim kv_out_dims; DDim q_transpose_out_dims; DDim kv_transpose_out_dims; DDim qk_out_dims; DDim softmax_out_dims; DDim qktv_out_dims; DDim gate_out_dims; GateAttentionConfig(const GPUContext& dev_ctx, const DenseTensor* query, const DenseTensor* key, const DenseTensor* query_weight, const DenseTensor* qkv_weight, bool merge_qkv, bool has_gating, bool use_flash_attn) : dev_ctx(dev_ctx), merge_qkv(merge_qkv), has_gating(has_gating), use_flash_attn(use_flash_attn) { // query: shape=[batch_size, seq_len_m, seq_len_r, q_dim] batch_size = query->dims()[0]; seq_len_m = query->dims()[1]; seq_len_r = query->dims()[2]; q_dim = query->dims()[3]; if (merge_qkv) { PADDLE_ENFORCE_NOT_NULL( qkv_weight, common::errors::NotFound("The input qkv_weight can not be nullptr " "when merge_qkv is true.")); // When q_dim == kv_dim, QKV matmul can be computed merged. // qkv_weight: shape=[3, num_heads, head_dim, q_dim] num_heads = qkv_weight->dims()[1]; head_dim = qkv_weight->dims()[2]; m_size = seq_len_r; kv_dim = q_dim; qkv_out_dims = {batch_size, seq_len_m, seq_len_r, 3, num_heads, head_dim}; qkv_transpose_out_dims = { 3, batch_size, seq_len_m, num_heads, seq_len_r, head_dim}; } else { PADDLE_ENFORCE_NOT_NULL( key, common::errors::NotFound( "The input key can not be nullptr when merge_qkv is false.")); PADDLE_ENFORCE_NOT_NULL( query_weight, common::errors::NotFound("The input query_weight can not be " "nullptr when merge_qkv is false.")); // When q_dim != kv_dim, QKV matmul must be computed saparately. // key: shape=[batch_size, seq_len_m, m_size, kv_dim] // query_w: shape=[q_dim, num_heads, head_dim] num_heads = query_weight->dims()[1]; head_dim = query_weight->dims()[2]; m_size = key->dims()[2]; kv_dim = key->dims()[3]; q_out_dims = {batch_size, seq_len_m, seq_len_r, num_heads, head_dim}; kv_out_dims = {batch_size, seq_len_m, m_size, num_heads, head_dim}; q_transpose_out_dims = { batch_size, seq_len_m, num_heads, seq_len_r, head_dim}; kv_transpose_out_dims = { batch_size, seq_len_m, num_heads, m_size, head_dim}; } qk_out_dims = {batch_size, seq_len_m, num_heads, seq_len_r, m_size}; softmax_out_dims = {batch_size, seq_len_m, num_heads, seq_len_r, m_size}; qktv_out_dims = {batch_size, seq_len_m, num_heads, seq_len_r, head_dim}; gate_out_dims = {batch_size, seq_len_m, seq_len_r, num_heads, head_dim}; } bool CanUseFlashAttn() const { #if defined(PADDLE_WITH_FLASHATTN) && !defined(PADDLE_WITH_HIP) if (!std::is_same::value && !std::is_same::value) { return false; } if (merge_qkv && batch_size == 1) { if (head_dim == 32 || head_dim == 64 || head_dim == 128) { return use_flash_attn; } } #endif return false; } int64_t GetQuerySize() const { return batch_size * seq_len_m * seq_len_r * num_heads * head_dim; } DenseTensor* GetQKVOut() { if (!qkv_out.IsInitialized()) { qkv_out.Resize(qkv_out_dims); AllocWithDebugInfo(dev_ctx, "qkv_out", &qkv_out); } return &qkv_out; } DenseTensor* GetQueryOut() { if (!query_out.IsInitialized()) { query_out.Resize(q_out_dims); AllocWithDebugInfo(dev_ctx, "query_out", &query_out); } return &query_out; } DenseTensor* GetKeyOut() { if (!key_out.IsInitialized()) { key_out.Resize(kv_out_dims); AllocWithDebugInfo(dev_ctx, "key_out", &key_out); } return &key_out; } DenseTensor* GetValueOut() { if (!value_out.IsInitialized()) { value_out.Resize(kv_out_dims); AllocWithDebugInfo(dev_ctx, "value_out", &value_out); } return &value_out; } DenseTensor* GetQKOut(DenseTensor* softmax_out) { // softmax_dim = qk_out_dim[-1] = qk_out_dim[rank - 1] int softmax_dim = m_size; if (!softmax_out || phi::UseCudnnSoftmax(dev_ctx, softmax_dim, true)) { // Not sure whether cudnn softmax can execute inplace. if (!qkv_out.IsInitialized()) { qk_out.Resize(qk_out_dims); AllocWithDebugInfo(dev_ctx, "qk_out", &qk_out); } return &qk_out; } else { // Enable inplace softmax. return softmax_out; } } DenseTensor* GetQKTVOut(DenseTensor* gate_out) { if (has_gating && gate_out) { // Reuse gate_out. gate_out->Resize(qktv_out_dims); return gate_out; } else { if (!qktv_out.IsInitialized()) { qktv_out.Resize(qktv_out_dims); AllocWithDebugInfo(dev_ctx, "qktv_out", &qktv_out); } return &qktv_out; } } void ClearQKVOut() { if (qkv_out.IsInitialized()) { qkv_out.clear(); } } void ClearQKOut() { if (qk_out.IsInitialized()) { qk_out.clear(); } } void ClearQKTVOut() { if (qktv_out.IsInitialized()) { qktv_out.clear(); } } protected: DenseTensor qkv_out; DenseTensor query_out; DenseTensor key_out; DenseTensor value_out; // qk_out = BatchedGEMM(Q, K^T) // qk_out: shape=[batch_size, seq_len_m, num_heads, seq_len_r, m_size] // softmax_out = softmax(qk_out + nonbatched_bias + src_mask) // The shape of qk_out, softmax_out is the same, thus can be called inplace. DenseTensor qk_out; // qktv_out may reuse gate_out. DenseTensor qktv_out; }; template struct GateAttentionGradConfig : public GateAttentionConfig { public: GateAttentionGradConfig(const GPUContext& dev_ctx, const DenseTensor* query, const DenseTensor* key, const DenseTensor* query_weight, const DenseTensor* qkv_weight, bool merge_qkv, bool has_gating, bool use_flash_attn) : GateAttentionConfig(dev_ctx, query, key, query_weight, qkv_weight, merge_qkv, has_gating, use_flash_attn) {} DenseTensor* GetQKVOutGrad() { if (!qkv_out_grad.IsInitialized()) { qkv_out_grad.Resize(this->qkv_out_dims); AllocWithDebugInfo(this->dev_ctx, "qkv_out_grad", &qkv_out_grad); } return &qkv_out_grad; } DenseTensor* GetQueryOutGrad() { if (!query_out_grad.IsInitialized()) { query_out_grad.Resize(this->q_out_dims); AllocWithDebugInfo(this->dev_ctx, "query_out_grad", &query_out_grad); } return &query_out_grad; } DenseTensor* GetKeyOutGrad() { if (!key_out_grad.IsInitialized()) { key_out_grad.Resize(this->kv_out_dims); AllocWithDebugInfo(this->dev_ctx, "key_out_grad", &key_out_grad); } return &key_out_grad; } DenseTensor* GetValueOutGrad() { if (!value_out_grad.IsInitialized()) { value_out_grad.Resize(this->kv_out_dims); AllocWithDebugInfo(this->dev_ctx, "value_out_grad", &value_out_grad); } return &value_out_grad; } DenseTensor* GetQKOutGrad(DenseTensor* softmax_out_grad) { // softmax_dim = qk_out_dim[-1] = qk_out_dim[rank - 1] int softmax_dim = this->m_size; if (!softmax_out_grad || phi::UseCudnnSoftmax(this->dev_ctx, softmax_dim, true)) { if (!qk_out_grad.IsInitialized()) { qk_out_grad.Resize(this->qk_out_dims); AllocWithDebugInfo(this->dev_ctx, "qk_out_grad", &qk_out_grad); } return &qk_out_grad; } else { return softmax_out_grad; } } protected: DenseTensor qkv_out_grad; DenseTensor query_out_grad; DenseTensor key_out_grad; DenseTensor value_out_grad; DenseTensor qk_out_grad; }; template class FMHAGateRef { public: FMHAGateRef(const GPUContext& dev_ctx, bool merge_qkv) : dev_ctx_(dev_ctx), merge_qkv_(merge_qkv) {} void ComputeForward(const DenseTensor* nonbatched_bias, const DenseTensor* src_mask, DenseTensor* q_transpose_out, DenseTensor* k_transpose_out, DenseTensor* v_transpose_out, DenseTensor* qkv_transpose_out, DenseTensor* softmax_out, DenseTensor* fmha_out, DenseTensor* gate_out, GateAttentionConfig* config) { T* q_ptr = nullptr; T* k_ptr = nullptr; T* v_ptr = nullptr; if (merge_qkv_) { // qkv_transpose_out = transpose(qkv_out) PADDLE_ENFORCE_NOT_NULL( qkv_transpose_out, common::errors::NotFound("The input qkv_transpose_out can not be " "nullptr when merge_qkv is true.")); DenseTensor* qkv_out = config->GetQKVOut(); ComputeQKVTransposeForward(*qkv_out, qkv_transpose_out); config->ClearQKVOut(); // q_size == k_size int64_t q_size = config->GetQuerySize(); q_ptr = qkv_transpose_out->data(); k_ptr = q_ptr + q_size; v_ptr = k_ptr + q_size; } else { PADDLE_ENFORCE_NOT_NULL( q_transpose_out, common::errors::NotFound("The input q_transpose_out can not be " "nullptr when merge_qkv is false.")); PADDLE_ENFORCE_NOT_NULL( k_transpose_out, common::errors::NotFound("The input k_transpose_out can not be " "nullptr when merge_qkv is false.")); PADDLE_ENFORCE_NOT_NULL( v_transpose_out, common::errors::NotFound("The input v_transpose_out can not be " "nullptr when merge_qkv is false.")); DenseTensor* query_out = config->GetQueryOut(); DenseTensor* key_out = config->GetKeyOut(); DenseTensor* value_out = config->GetValueOut(); ComputeQKVTransposeForward(*query_out, *key_out, *value_out, q_transpose_out, k_transpose_out, v_transpose_out); // q_size != k_size q_ptr = q_transpose_out->data(); k_ptr = k_transpose_out->data(); v_ptr = v_transpose_out->data(); } // qk_out = BatchedGEMM(Q, K^T) // [batch_size, seq_len_m, num_heads, seq_len_r, head_dim] * // [batch_size, seq_len_m, num_heads, m_size, head_dim] // -> [batch_size, seq_len_m, num_heads, seq_len_r, m_size] DenseTensor* qk_out = config->GetQKOut(softmax_out); T* qk_out_ptr = qk_out->data(); int64_t gemm_batch_size = config->batch_size * config->seq_len_m * config->num_heads; int64_t gemm_m = config->seq_len_r; int64_t gemm_n = config->m_size; int64_t gemm_k = config->head_dim; T alpha = static_cast(1.0 / sqrt(config->head_dim)); // attn = matmul(q, k.transpose(-1, -2)) ComputeBatchedGEMM(q_ptr, k_ptr, qk_out_ptr, false, true, gemm_m, gemm_n, gemm_k, gemm_batch_size, alpha); // attn = softmax_dropout(attn, 0, self.training, mask=mask, bias=bias) // softmax_out = softmax(qk_out + nonbatched_bias + src_mask) ComputeBiasMaskSoftmaxForward( nonbatched_bias, src_mask, qk_out, softmax_out); config->ClearQKOut(); // qktv_out = BatchedGEMM(softmax_out, V) // [batch_size, seq_len_m, num_heads, seq_len_r, m_size] * // [batch_size, seq_len_m, num_heads, m_size, head_dim] // -> [batch_size, seq_len_m, num_heads, seq_len_r, head_dim] DenseTensor* qktv_out = config->GetQKTVOut(gate_out); T* qktv_out_ptr = qktv_out->data(); gemm_m = config->seq_len_r; gemm_n = config->head_dim; gemm_k = config->m_size; // o = matmul(attn, v) T* softmax_out_ptr = softmax_out->data(); ComputeBatchedGEMM(softmax_out_ptr, v_ptr, qktv_out_ptr, false, false, gemm_m, gemm_n, gemm_k, gemm_batch_size); // fmha_out = transpose(qktv_out) // o = o.transpose(-2, -3).contiguous() ComputeQKTVTransposeForward(*qktv_out, fmha_out); config->ClearQKTVOut(); if (config->has_gating) { gate_out->Resize(config->gate_out_dims); } } void ComputeBackward(const DenseTensor* q_transpose_out, const DenseTensor* k_transpose_out, const DenseTensor* v_transpose_out, const DenseTensor* qkv_transpose_out, const DenseTensor* softmax_out, const DenseTensor* fmha_out_grad, DenseTensor* src_mask_grad, DenseTensor* nonbatched_bias_grad, GateAttentionGradConfig* config) { const T* q_ptr = nullptr; const T* k_ptr = nullptr; const T* v_ptr = nullptr; T* q_grad_ptr = nullptr; T* k_grad_ptr = nullptr; T* v_grad_ptr = nullptr; DenseTensor q_transpose_out_grad; DenseTensor k_transpose_out_grad; DenseTensor v_transpose_out_grad; DenseTensor qkv_transpose_out_grad; if (merge_qkv_) { PADDLE_ENFORCE_NOT_NULL( qkv_transpose_out, common::errors::NotFound("The input qkv_transpose_out can not be " "nullptr when merge_qkv is true.")); int64_t q_size = config->GetQuerySize(); q_ptr = qkv_transpose_out->data(); k_ptr = q_ptr + q_size; v_ptr = k_ptr + q_size; qkv_transpose_out_grad.Resize(config->qkv_transpose_out_dims); AllocWithDebugInfo( dev_ctx_, "qkv_transpose_out_grad", &qkv_transpose_out_grad); q_grad_ptr = qkv_transpose_out_grad.data(); k_grad_ptr = q_grad_ptr + q_size; v_grad_ptr = k_grad_ptr + q_size; } else { PADDLE_ENFORCE_NOT_NULL( q_transpose_out, common::errors::NotFound("The input q_transpose_out can not be " "nullptr when merge_qkv is false.")); PADDLE_ENFORCE_NOT_NULL( k_transpose_out, common::errors::NotFound("The input k_transpose_out can not be " "nullptr when merge_qkv is false.")); PADDLE_ENFORCE_NOT_NULL( v_transpose_out, common::errors::NotFound("The input v_transpose_out can not be " "nullptr when merge_qkv is false.")); q_ptr = q_transpose_out->data(); k_ptr = k_transpose_out->data(); v_ptr = v_transpose_out->data(); q_transpose_out_grad.Resize(config->q_transpose_out_dims); k_transpose_out_grad.Resize(config->kv_transpose_out_dims); v_transpose_out_grad.Resize(config->kv_transpose_out_dims); q_grad_ptr = dev_ctx_.Alloc(&q_transpose_out_grad, q_transpose_out_grad.numel() * sizeof(T)); k_grad_ptr = dev_ctx_.Alloc(&k_transpose_out_grad, k_transpose_out_grad.numel() * sizeof(T)); v_grad_ptr = dev_ctx_.Alloc(&v_transpose_out_grad, v_transpose_out_grad.numel() * sizeof(T)); } DenseTensor softmax_out_grad; softmax_out_grad.Resize(config->softmax_out_dims); AllocWithDebugInfo(dev_ctx_, "softmax_out_grad", &softmax_out_grad); int64_t gemm_batch_size = config->batch_size * config->seq_len_m * config->num_heads; { // Forward: fmha_out = transpose(qktv_out) DenseTensor qktv_out_grad; qktv_out_grad.Resize(config->qktv_out_dims); AllocWithDebugInfo(dev_ctx_, "qktv_out_grad", &qktv_out_grad); ComputeQKTVTransposeBackward(*fmha_out_grad, &qktv_out_grad); // Forward: qktv_out = BatchedGEMM(softmax_out, V) // Backward: // V_grad = BatchedGEMM(softmax_out^T, qktv_out_grad) (dy = x^T * dout) int64_t gemm_m = config->m_size; int64_t gemm_n = config->head_dim; int64_t gemm_k = config->seq_len_r; const T* softmax_out_ptr = softmax_out->data(); const T* qktv_out_grad_ptr = qktv_out_grad.data(); ComputeBatchedGEMM(softmax_out_ptr, qktv_out_grad_ptr, v_grad_ptr, true, false, gemm_m, gemm_n, gemm_k, gemm_batch_size); // Backward: softmax_out_grad = qktv_out_grad * V^T (dx = dout * y^T) gemm_m = config->seq_len_r; gemm_n = config->m_size; gemm_k = config->head_dim; T* softmax_out_grad_ptr = softmax_out_grad.data(); ComputeBatchedGEMM(qktv_out_grad_ptr, v_ptr, softmax_out_grad_ptr, false, true, gemm_m, gemm_n, gemm_k, gemm_batch_size); } DenseTensor* qk_out_grad = config->GetQKOutGrad(&softmax_out_grad); ComputeBiasMaskSoftmaxBackward(&softmax_out_grad, softmax_out, src_mask_grad, qk_out_grad, nonbatched_bias_grad); // Forward: qk_out = BatchedGEMM(Q, K^T) // Backward: k_grad = BatchedGEMM(qk_out_grad^T, Q) (dy = dout^t * x) int64_t gemm_m = config->m_size; int64_t gemm_n = config->head_dim; int64_t gemm_k = config->seq_len_r; T alpha = static_cast(1.0 / sqrt(config->head_dim)); T* qk_out_grad_ptr = qk_out_grad->data(); ComputeBatchedGEMM(qk_out_grad_ptr, q_ptr, k_grad_ptr, true, false, gemm_m, gemm_n, gemm_k, gemm_batch_size, alpha); // Backward: q_grad = BatchedGEMM(qk_out_grad, K) (dx = dout * y) gemm_m = config->seq_len_r; gemm_n = config->head_dim; gemm_k = config->m_size; ComputeBatchedGEMM(qk_out_grad_ptr, k_ptr, q_grad_ptr, false, false, gemm_m, gemm_n, gemm_k, gemm_batch_size, alpha); if (merge_qkv_) { DenseTensor* qkv_out_grad = config->GetQKVOutGrad(); ComputeQKVTransposeBackward(qkv_transpose_out_grad, qkv_out_grad); } else { DenseTensor* q_out_grad = config->GetQueryOutGrad(); DenseTensor* k_out_grad = config->GetKeyOutGrad(); DenseTensor* v_out_grad = config->GetValueOutGrad(); ComputeQKVTransposeBackward(q_transpose_out_grad, k_transpose_out_grad, v_transpose_out_grad, q_out_grad, k_out_grad, v_out_grad); } } void ComputeQKVTransposeForward(const DenseTensor& q_out, const DenseTensor& k_out, const DenseTensor& v_out, DenseTensor* q_transpose_out, DenseTensor* k_transpose_out, DenseTensor* v_transpose_out) { std::vector perm = {0, 1, 3, 2, 4}; funcs::TransposeGPUKernelDriver(dev_ctx_, q_out, perm, q_transpose_out); funcs::TransposeGPUKernelDriver(dev_ctx_, k_out, perm, k_transpose_out); funcs::TransposeGPUKernelDriver(dev_ctx_, v_out, perm, v_transpose_out); } void ComputeQKVTransposeBackward(const DenseTensor& q_transpose_out_grad, const DenseTensor& k_transpose_out_grad, const DenseTensor& v_transpose_out_grad, DenseTensor* q_out_grad, DenseTensor* k_out_grad, DenseTensor* v_out_grad) { std::vector perm = {0, 1, 3, 2, 4}; funcs::TransposeGPUKernelDriver( dev_ctx_, q_transpose_out_grad, perm, q_out_grad); funcs::TransposeGPUKernelDriver( dev_ctx_, k_transpose_out_grad, perm, k_out_grad); funcs::TransposeGPUKernelDriver( dev_ctx_, v_transpose_out_grad, perm, v_out_grad); } // [batch_size, seq_len_m, seq_len_r, 3, num_heads, head_dim] -> // [3, batch_size, seq_len_m, num_heads, seq_len_r, head_dim] void ComputeQKVTransposeForward(const DenseTensor& qkv_out, DenseTensor* qkv_transpose_out) { std::vector perm = {3, 0, 1, 4, 2, 5}; funcs::TransposeGPUKernelDriver( dev_ctx_, qkv_out, perm, qkv_transpose_out); } void ComputeQKVTransposeBackward(const DenseTensor& qkv_transpose_out_grad, DenseTensor* qkv_out_grad) { std::vector perm = {1, 2, 4, 0, 3, 5}; funcs::TransposeGPUKernelDriver( dev_ctx_, qkv_transpose_out_grad, perm, qkv_out_grad); } // [batch_size, seq_len_m, num_head, seq_len_r, c] -> // [batch_size, seq_len_m, seq_len_r, num_head, c] void ComputeQKTVTransposeForward(const DenseTensor& qktv_out, DenseTensor* fmha_out) { std::vector perm = {0, 1, 3, 2, 4}; funcs::TransposeGPUKernelDriver(dev_ctx_, qktv_out, perm, fmha_out); } void ComputeQKTVTransposeBackward(const DenseTensor& fmha_out_grad, DenseTensor* qktv_out_grad) { std::vector perm = {0, 1, 3, 2, 4}; funcs::TransposeGPUKernelDriver( dev_ctx_, fmha_out_grad, perm, qktv_out_grad); } // qk_out = qk_out + nonbatched_bias + src_mask // softmax_out = softmax(src_mask_out) void ComputeBiasMaskSoftmaxForward(const DenseTensor* nonbatched_bias, const DenseTensor* src_mask, DenseTensor* qk_out, DenseTensor* softmax_out) { if (nonbatched_bias) { std::vector ins = {qk_out, src_mask, nonbatched_bias}; std::vector outs = {qk_out}; funcs::BroadcastKernel(dev_ctx_, ins, &outs, TernaryAddFunctor()); } else { std::vector ins = {qk_out, src_mask}; std::vector outs = {qk_out}; funcs::BroadcastKernel(dev_ctx_, ins, &outs, funcs::AddFunctor()); } phi::SoftmaxForwardCUDAKernelDriver(dev_ctx_, *qk_out, -1, softmax_out); } // src_mask_out = qk_out + nonbatched_bias + src_mask // softmax_out = softmax(src_mask_out) void ComputeBiasMaskSoftmaxBackward(const DenseTensor* softmax_out_grad, const DenseTensor* softmax_out, DenseTensor* src_mask_grad, DenseTensor* qk_out_grad, DenseTensor* nonbatched_bias_grad) { PADDLE_ENFORCE_NOT_NULL( qk_out_grad, common::errors::NotFound("The qk_out_grad can not be nullptr.")); PADDLE_ENFORCE_EQ(qk_out_grad->dims(), softmax_out->dims(), common::errors::InvalidArgument( "The shape of qk_out_grad and softmax_out is " "expected to be the same. But received qk_out_grad's " "shape = %s, softmax_out's shape = %s.", qk_out_grad->dims(), softmax_out->dims())); PADDLE_ENFORCE_EQ(src_mask_grad, nullptr, common::errors::InvalidArgument( "src_mask_grad is expected to be nullptr.")); phi::SoftmaxBackwardCUDAKernelDriver( dev_ctx_, *softmax_out, *softmax_out_grad, -1, qk_out_grad); if (nonbatched_bias_grad) { // [batch_size, seq_len_m, num_heads, seq_len_r, m_size] -> // [batch_size, 1, num_heads, seq_len_r, m_size] funcs::ReduceKernel>( dev_ctx_, *qk_out_grad, nonbatched_bias_grad, kps::IdentityFunctor(), {1}); } } private: void ComputeBatchedGEMM(const T* a_ptr, const T* b_ptr, T* c_ptr, bool trans_a, bool trans_b, int64_t m, int64_t n, int64_t k, int64_t batch_size, T alpha = static_cast(1.0), T beta = static_cast(0.0)) { int64_t stride_a = m * k; int64_t stride_b = k * n; CBLAS_TRANSPOSE cblas_trans_a = trans_a ? CblasTrans : CblasNoTrans; CBLAS_TRANSPOSE cblas_trans_b = trans_b ? CblasTrans : CblasNoTrans; auto blas = funcs::GetBlas(dev_ctx_); blas.BatchedGEMM(cblas_trans_a, cblas_trans_b, m, n, k, alpha, a_ptr, b_ptr, beta, c_ptr, batch_size, stride_a, stride_b); } const GPUContext& dev_ctx_; bool merge_qkv_; }; template class FlashAttnWithGating { public: FlashAttnWithGating(const GPUContext& dev_ctx, bool merge_qkv) : dev_ctx_(dev_ctx), merge_qkv_(merge_qkv) {} void ComputeForward(const DenseTensor* nonbatched_bias, const DenseTensor* src_mask, DenseTensor* qkv_transpose_out, DenseTensor* softmax_lse, DenseTensor* fmha_out, GateAttentionConfig* config) { #if defined(PADDLE_WITH_FLASHATTN) && !defined(PADDLE_WITH_HIP) bool is_bf16 = qkv_transpose_out->dtype() == DataType::BFLOAT16 ? true : false; TypeDebugInfo(); PADDLE_ENFORCE_NOT_NULL( qkv_transpose_out, common::errors::NotFound("The input qkv_transpose_out can not be " "nullptr when merge_qkv is true.")); // 1. Transpose qkv_out for flash_attn. DenseTensor* qkv_out = config->GetQKVOut(); ComputeQKVTransposeForward(*qkv_out, qkv_transpose_out); config->ClearQKVOut(); // q_size == k_size int64_t q_size = config->GetQuerySize(); T* q_ptr = qkv_transpose_out->data(); T* k_ptr = q_ptr + q_size; T* v_ptr = k_ptr + q_size; // 2. Scale Q: q_ptr = alpha * q_ptr ComputeScaleQ(q_size, config->head_dim, q_ptr); // 3. flash_attn parameter setting. DenseTensor cu_seq_q; DenseTensor cu_seq_k; InitArgumentsAndSeqTensors(config, &cu_seq_q, &cu_seq_k); std::vector temp_mask_dim = GetCompressedDim(src_mask); std::vector temp_bias_dim = GetCompressedDim(nonbatched_bias); softmax_lse->Resize({fa_batch_size_, fa_num_heads_, fa_softmax_lse_dim_}); AllocWithDebugInfo(dev_ctx_, "softmax_lse", softmax_lse); if (VLOG_IS_ON(6)) { VLOG(6) << "temp_mask_dim={" << make_ddim(temp_mask_dim) << "}"; VLOG(6) << "temp_bias_dim={" << make_ddim(temp_bias_dim) << "}"; VLOG(6) << TensorDebugString(&cu_seq_q, "cu_seq_q"); VLOG(6) << TensorDebugString(&cu_seq_k, "cu_seq_k"); VLOG(6) << TensorDebugString(nonbatched_bias, "nonbatched_bias"); VLOG(6) << TensorDebugString(src_mask, "src_mask"); VLOG(6) << TensorDebugString(qkv_transpose_out, "qkv_transpose_out"); VLOG(6) << TensorDebugString(softmax_lse, "softmax_lse"); VLOG(6) << TensorDebugString(fmha_out, "fmha_out"); } // 4. Get workspace size and run the flash-attention kernel. uint64_t workspace_size = 0; DenseTensor workspace; cudaStream_t stream = dev_ctx_.stream(); for (bool need_calc : {false, true}) { // first calling, need_calc=false, set out_ptr to nullptr to calculate // workspace size second calling, need_calc=true, run flash-attention // kernel. void* out_ptr = need_calc ? static_cast(fmha_out->data()) : nullptr; void* workspace_ptr = nullptr; if (need_calc) { VLOG(6) << "Step 2: Call the flash-attention kernel"; if (workspace_size > 0) { workspace = CreateWorkspace(workspace_size); workspace_ptr = static_cast(workspace.data()); } } else { VLOG(6) << "Step 1: Calculate the workspace_size"; } bool succ = phi::dynload::flash_attn_fwd_with_bias_and_mask( static_cast(q_ptr), static_cast(k_ptr), static_cast(v_ptr), out_ptr, // set out to nullptr to calculate workspace size cu_seq_q.data(), cu_seq_k.data(), fa_total_q_, fa_total_k_, fa_batch_size_, fa_num_heads_, fa_head_size_, fa_max_seqlen_q_, fa_max_seqlen_k_, fa_dropout_prob_, fa_softmax_scale_, fa_zero_tensors_, is_bf16, fa_num_splits_, softmax_lse->data(), workspace_ptr, &workspace_size, stream, fa_seed_, fa_offset_, src_mask ? src_mask->data() : nullptr, nonbatched_bias ? nonbatched_bias->data() : nullptr, src_mask ? temp_mask_dim.data() : nullptr, nonbatched_bias ? temp_bias_dim.data() : nullptr); PADDLE_ENFORCE_EQ( succ, true, common::errors::External(phi::dynload::flash_attn_error())); WaitWithDebugInfo(dev_ctx_); } #else PADDLE_THROW(common::errors::Unimplemented( "FlashAttention is unsupported, please set use_flash_attn to false.")); #endif } void ComputeBackward(const DenseTensor* qkv_transpose_out, const DenseTensor* src_mask, const DenseTensor* nonbatched_bias, const DenseTensor* softmax_lse, const DenseTensor* fmha_out, const DenseTensor* fmha_out_grad, DenseTensor* src_mask_grad, DenseTensor* nonbatched_bias_grad, GateAttentionGradConfig* config) { #if defined(PADDLE_WITH_FLASHATTN) && !defined(PADDLE_WITH_HIP) bool is_bf16 = qkv_transpose_out->dtype() == DataType::BFLOAT16 ? true : false; TypeDebugInfo(); PADDLE_ENFORCE_NOT_NULL( qkv_transpose_out, common::errors::NotFound("The input qkv_transpose_out can not be " "nullptr when merge_qkv is true.")); int64_t q_size = config->GetQuerySize(); const T* q_ptr = qkv_transpose_out->data(); const T* k_ptr = q_ptr + q_size; const T* v_ptr = k_ptr + q_size; DenseTensor qkv_transpose_out_grad; qkv_transpose_out_grad.Resize({3, config->batch_size, config->seq_len_m, config->seq_len_r, config->num_heads, config->head_dim}); AllocWithDebugInfo( dev_ctx_, "qkv_transpose_out_grad", &qkv_transpose_out_grad); T* q_grad_ptr = qkv_transpose_out_grad.data(); T* k_grad_ptr = q_grad_ptr + q_size; T* v_grad_ptr = k_grad_ptr + q_size; WaitWithDebugInfo(dev_ctx_); // 1. flash_attn parameter setting. DenseTensor cu_seq_q; DenseTensor cu_seq_k; InitArgumentsAndSeqTensors(config, &cu_seq_q, &cu_seq_k); const int32_t* cu_seq_q_ptr = cu_seq_q.data(); const int32_t* cu_seq_k_ptr = cu_seq_k.data(); std::vector temp_mask_dim = GetCompressedDim(src_mask); std::vector temp_bias_dim = GetCompressedDim(nonbatched_bias); DenseTensor softmax_d; softmax_d.Resize(softmax_lse->dims()); AllocWithDebugInfo(dev_ctx_, "d_softmax_lse", &softmax_d); DenseTensor bias_d; if (nonbatched_bias) { bias_d.Resize( {fa_batch_size_, fa_num_heads_, fa_max_seqlen_q_, fa_max_seqlen_k_}); AllocWithDebugInfo(dev_ctx_, "d_bias", &bias_d); } if (VLOG_IS_ON(6)) { VLOG(6) << TensorDebugString(fmha_out, "fmha_out"); VLOG(6) << TensorDebugString(fmha_out_grad, "fmha_out_grad"); VLOG(6) << TensorDebugString(softmax_lse, "softmax_lse"); VLOG(6) << TensorDebugString(&softmax_d, "softmax_d"); VLOG(6) << TensorDebugString(nonbatched_bias, "nonbatched_bias"); VLOG(6) << TensorDebugString(&bias_d, "bias_d"); } // 2. Get workspace size and run the flash-attention kernel. uint64_t workspace_size = 0; DenseTensor workspace; cudaStream_t stream = dev_ctx_.stream(); for (bool need_calc : {false, true}) { // first calling, need_calc=false, set out_ptr to nullptr to calculate // workspace size second calling, need_calc=true, run flash-attention // kernel. const void* out_ptr = need_calc ? static_cast(fmha_out->data()) : nullptr; void* workspace_ptr = nullptr; if (need_calc) { VLOG(6) << "Step 2: Call the flash-attention kernel"; if (workspace_size > 0) { workspace = CreateWorkspace(workspace_size); workspace_ptr = static_cast(workspace.data()); } } else { VLOG(6) << "Step 1: Calculate the workspace_size"; } bool succ = phi::dynload::flash_attn_bwd_with_bias_and_mask( static_cast(q_ptr), static_cast(k_ptr), static_cast(v_ptr), static_cast(q_grad_ptr), static_cast(k_grad_ptr), static_cast(v_grad_ptr), out_ptr, // set out to nullptr to calculate workspace size static_cast(fmha_out_grad->data()), cu_seq_q_ptr, cu_seq_k_ptr, fa_total_q_, fa_total_k_, fa_batch_size_, fa_num_heads_, fa_head_size_, fa_max_seqlen_q_, fa_max_seqlen_k_, fa_dropout_prob_, fa_softmax_scale_, fa_zero_tensors_, is_bf16, fa_num_splits_, softmax_lse->data(), softmax_d.data(), nonbatched_bias ? bias_d.data() : nullptr, workspace_ptr, &workspace_size, stream, fa_seed_, fa_offset_, src_mask ? src_mask->data() : nullptr, nonbatched_bias ? nonbatched_bias->data() : nullptr, src_mask ? temp_mask_dim.data() : nullptr, nonbatched_bias ? temp_bias_dim.data() : nullptr); PADDLE_ENFORCE_EQ( succ, true, common::errors::External(phi::dynload::flash_attn_error())); WaitWithDebugInfo(dev_ctx_); } if (nonbatched_bias) { // compare block reduce auto dbias_first_dim = bias_d.numel() / nonbatched_bias->numel(); bias_d.Resize({dbias_first_dim, temp_bias_dim[0], temp_bias_dim[1], temp_bias_dim[2], temp_bias_dim[3]}); funcs::ReduceKernel>( dev_ctx_, bias_d, nonbatched_bias_grad, kps::IdentityFunctor(), {0}); } // 3. Scale Q's grad: q_grad_ptr = alpha * q_grad_ptr ComputeScaleQ(q_size, config->head_dim, q_grad_ptr); // 4. Compute the grad of qkv_out. DenseTensor* qkv_out_grad = config->GetQKVOutGrad(); ComputeQKVTransposeBackward(qkv_transpose_out_grad, qkv_out_grad); #else PADDLE_THROW(common::errors::Unimplemented( "FlashAttention is unsupported, please set use_flash_attn to false.")); #endif } private: std::vector GetCompressedDim(const DenseTensor* tensor) { std::vector compressed_dims; if (tensor) { int64_t first_dim = 1; const auto& origin_dims = tensor->dims(); auto rank = origin_dims.size(); for (int i = 0; i < rank - 3; ++i) { first_dim *= origin_dims[i]; } compressed_dims = {first_dim, origin_dims[rank - 3], origin_dims[rank - 2], origin_dims[rank - 1]}; } return compressed_dims; } DenseTensor CreateWorkspace(uint64_t workspace_size) { DenseTensor workspace; if (workspace_size > 0) { workspace = Empty( dev_ctx_, {int64_t(workspace_size / sizeof(float))}); } VLOG(5) << "Allocate workspace: workspace_size=" << workspace_size; return workspace; } void GenerateSeedAndOffset(int64_t batch_size, int64_t num_heads) { auto gen = dev_ctx_.GetGenerator(); uint64_t inc = batch_size * num_heads * 32; auto seed_offset_pair = gen->IncrementOffset(inc); fa_seed_ = seed_offset_pair.first; fa_offset_ = seed_offset_pair.second; } void InitArgumentsAndSeqTensors(GateAttentionConfig* config, DenseTensor* cu_seq_q, DenseTensor* cu_seq_k) { fa_batch_size_ = static_cast(config->batch_size) * static_cast(config->seq_len_m); fa_num_heads_ = static_cast(config->num_heads); // qkv_dims[2]; fa_head_size_ = static_cast(config->head_dim); // qkv_dims[3]; fa_max_seqlen_q_ = config->seq_len_r; fa_max_seqlen_k_ = config->m_size; fa_total_q_ = fa_batch_size_ * fa_max_seqlen_q_; fa_total_k_ = fa_batch_size_ * fa_max_seqlen_k_; // 0 for an internal heuristic, which is optimal fa_num_splits_ = 0; fa_zero_tensors_ = false; fa_softmax_lse_dim_ = ((fa_max_seqlen_q_ + 16 - 1) / 16) * 16; fa_softmax_scale_ = 1.0f; fa_dropout_prob_ = 0.0f; GenerateSeedAndOffset(fa_batch_size_, fa_num_heads_); phi::ArangeNullaryKernel( dev_ctx_, 0, (fa_batch_size_ + 1) * fa_max_seqlen_q_, fa_max_seqlen_q_, cu_seq_q); phi::ArangeNullaryKernel( dev_ctx_, 0, (fa_batch_size_ + 1) * fa_max_seqlen_k_, fa_max_seqlen_k_, cu_seq_k); if (VLOG_IS_ON(6)) { VLOG(6) << "fa_batch_size : " << fa_batch_size_; VLOG(6) << "fa_total_q : " << fa_total_q_; VLOG(6) << "fa_total_k : " << fa_total_k_; VLOG(6) << "fa_num_heads : " << fa_num_heads_; VLOG(6) << "fa_head_size : " << fa_head_size_; VLOG(6) << "fa_max_seqlen_q : " << fa_max_seqlen_q_; VLOG(6) << "fa_max_seqlen_k : " << fa_max_seqlen_k_; VLOG(6) << "fa_num_splits : " << fa_num_splits_; VLOG(6) << "fa_softmax_lse_dim : " << fa_softmax_lse_dim_; VLOG(6) << "fa_softmax_scale : " << fa_softmax_scale_; VLOG(6) << "fa_dropout_prob : " << fa_dropout_prob_; } } // [batch_size, seq_len_m, seq_len_r, 3, num_heads, head_dim] -> // [3, batch_size, seq_len_m, seq_len_r, num_heads, head_dim] void ComputeQKVTransposeForward(const DenseTensor& qkv_out, DenseTensor* qkv_transpose_out) { std::vector perm = {3, 0, 1, 2, 4, 5}; funcs::TransposeGPUKernelDriver( dev_ctx_, qkv_out, perm, qkv_transpose_out); } // [3, batch_size, seq_len_m, seq_len_r, num_heads, head_dim] -> // [batch_size, seq_len_m, seq_len_r, 3, num_heads, head_dim] void ComputeQKVTransposeBackward(const DenseTensor& qkv_transpose_out_grad, DenseTensor* qkv_out_grad) { std::vector perm = {1, 2, 3, 0, 4, 5}; funcs::TransposeGPUKernelDriver( dev_ctx_, qkv_transpose_out_grad, perm, qkv_out_grad); } void ComputeScaleQ(int64_t numel, int64_t head_dim, T* ptr) { float scale = static_cast(1.0f / std::sqrt(head_dim)); VLOG(6) << "[ComputeScaleQ] numel=" << numel << ", scale=" << scale; auto gpu_config = phi::backends::gpu::GetGpuLaunchConfig1D(dev_ctx_, numel, 1); SimpleScaleKernel<<>>(numel, scale, ptr); } const GPUContext& dev_ctx_; bool merge_qkv_; int fa_batch_size_; int fa_total_q_; int fa_total_k_; int fa_num_heads_; int fa_head_size_; int fa_max_seqlen_q_; int fa_max_seqlen_k_; int fa_num_splits_; int fa_softmax_lse_dim_; float fa_softmax_scale_{1.0f}; float fa_dropout_prob_{0.0f}; uint64_t fa_seed_{0}; uint64_t fa_offset_{0}; bool fa_zero_tensors_{false}; }; } // namespace funcs } // namespace phi