// 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/kernels/flash_attn_kernel.h" #include "paddle/phi/backends/xpu/enforce_xpu.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/full_kernel.h" #include "paddle/phi/kernels/slice_kernel.h" #include "paddle/phi/kernels/xpu/flash_attn_utils.h" #include "xfa/flash_api.h" namespace phi { #define MHA_VARLEN_FWD_TYPES_AND_ARGS(T, TACCUM, TGEMM, TID) \ xpu::Context *ctx, const T *q, const T *k, const T *v, T *out, \ TACCUM *softmax_lse, const xpu::VectorParam&lod_seqlens_q, \ const xpu::VectorParam&lod_seqlens_k, int64_t max_seqlen_q, \ int64_t max_seqlen_k, int64_t head_num, int64_t head_num_k, \ int64_t head_dim, const float softmax_scale, const float p_dropout, \ int seed, const bool is_causal, const TACCUM *attn_mask, \ const TACCUM *bias, const float *q_maxptr, const float *k_maxptr, \ const float *v_maxptr, float *o_maxptr, const bool is_qkv_fusion, \ const int64_t qkv_layout, const float *alibi_slopes, \ const std::vector&alibi_slopes_shape, int window_size_left, \ int window_size_right, int64_t v_head_dim, \ const int *downstart_row_indices_data, \ const int *downend_row_indices_data, \ const int *upstart_row_indices_data, const int *upend_row_indices_data, \ const int flash_mask_head_num, int *flashmask_maxmin, \ XPUStream side_stream, int64_t fixlen_batch_num, bool unpadded_lse #define MHA_VARLEN_FWD_ARGS \ ctx, q, k, v, out, softmax_lse, lod_seqlens_q, lod_seqlens_k, max_seqlen_q, \ max_seqlen_k, head_num, head_num_k, head_dim, softmax_scale, p_dropout, \ seed, is_causal, attn_mask, bias, q_maxptr, k_maxptr, v_maxptr, \ o_maxptr, is_qkv_fusion, qkv_layout, alibi_slopes, alibi_slopes_shape, \ window_size_left, window_size_right, v_head_dim, \ downstart_row_indices_data, downend_row_indices_data, \ upstart_row_indices_data, upend_row_indices_data, flash_mask_head_num, \ flashmask_maxmin, side_stream, fixlen_batch_num, unpadded_lse template int mha_varlen_fwd_wrapper( MHA_VARLEN_FWD_TYPES_AND_ARGS(T, TACCUM, TGEMM, TID)) { PADDLE_THROW( "Unsupported template params combination for mha_varlen_fwd, should not " "reach here."); } #define DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(T, TACCUM, TGEMM, TID) \ template <> \ int mha_varlen_fwd_wrapper( \ MHA_VARLEN_FWD_TYPES_AND_ARGS(T, TACCUM, TGEMM, TID)) { \ return baidu::xpu::xfa::mha_varlen_fwd( \ MHA_VARLEN_FWD_ARGS); \ } DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(float, float, float, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(float, float, tfloat32, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeFP16, XPUTypeFP16, XPUTypeFP16, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeFP16, float, XPUTypeFP16, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeFP16, float, float, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeFP16, float, tfloat32, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, float, float, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, float, tfloat32, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, float, XPUTypeFP16, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, XPUTypeFP16, XPUTypeFP16, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, XPUTypeBF16, XPUTypeBF16, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, float, XPUTypeBF16, int); DECLARE_SUPPORTED_MHA_VARLEN_FWD_TYPES(XPUTypeBF16, XPUTypeFP16, XPUTypeBF16, int); #define MHA_VARLEN_FWD(T1, T2, T3, T4) \ mha_varlen_fwd_wrapper( \ dev_ctx.x_context(), \ q_data, \ k_data, \ v_data, \ out_data, \ softmax_lse_data, \ lod_seqlen_q, \ lod_seqlen_k, \ max_seqlen_q, \ max_seqlen_k, \ num_heads, \ num_heads_k, \ head_size, \ real_scale, \ real_dropout, \ static_cast(seed_offset_data[0]), \ causal, \ nullptr, \ bias_data, \ nullptr, \ nullptr, \ nullptr, \ nullptr, \ false, \ fa_layout, \ nullptr, \ {}, \ -1, \ -1, \ head_size_v, \ (const int*)downstart_row_indices_data, \ (const int*)downend_row_indices_data, \ (const int*)upstart_row_indices_data, \ (const int*)upend_row_indices_data, \ is_flashmask ? startend_row_indices->dims()[1] : 0, \ nullptr, \ is_flashmask ? flashmask_stream : nullptr, \ 0, \ false) template void FlashAttnKernelBase(const Context& dev_ctx, const DenseTensor& q, const DenseTensor& k, const DenseTensor& v, const api::VectorParam& lod_seqlen_q, const api::VectorParam& lod_seqlen_k, const optional& fixed_seed_offset, const optional& attn_mask, const optional& startend_row_indices, const int64_t batch_size, const Scalar& max_seqlen_q_, const Scalar& max_seqlen_k_, const int64_t num_heads, const int64_t num_heads_k, const int64_t head_size, const int64_t head_size_v, float scale, float dropout, bool causal, bool return_softmax, bool is_test, const std::string& rng_name, DenseTensor* out, DenseTensor* softmax, DenseTensor* softmax_lse, DenseTensor* seed_offset) { // Handle 0-size tensors: return zeros without calling XPU kernel // to avoid invalid memory access if (q.numel() == 0 || k.numel() == 0 || v.numel() == 0) { if (out) { Full(dev_ctx, out->dims(), 0, out); } if (softmax) { Full(dev_ctx, softmax->dims(), 0, softmax); } if (softmax_lse) { std::vector softmax_lse_dims = { batch_size, num_heads, max_seqlen_q_.to()}; softmax_lse->Resize(softmax_lse_dims); Full(dev_ctx, softmax_lse->dims(), 0, softmax_lse); } if (seed_offset) { Full(dev_ctx, seed_offset->dims(), 0, seed_offset); } return; } xpu::ctx_guard RAII_GUARD(dev_ctx.x_context()); float real_scale = scale == 0.0f ? 1.0f / std::sqrt(head_size) : scale; float real_dropout = is_test ? 0.0f : dropout; // output: softmax_lse, 训练参数,给反向用于反向重计算的L int64_t max_seqlen_q = max_seqlen_q_.to(); int64_t max_seqlen_k = max_seqlen_k_.to(); std::vector softmax_lse_dims = {batch_size, num_heads, max_seqlen_q}; softmax_lse->Resize(softmax_lse_dims); dev_ctx.template Alloc(softmax_lse); // output: o dev_ctx.template Alloc(out); // output: seed_offset seed_offset->Resize({2}); int64_t* seed_offset_data = dev_ctx.template HostAlloc(seed_offset); phi::GenerateRNGState(dev_ctx, fixed_seed_offset, seed_offset_data, rng_name, batch_size, num_heads); // raw pointers using XPUType = typename XPUTypeTrait::Type; const XPUType* q_data = reinterpret_cast(q.data()); const XPUType* k_data = reinterpret_cast(k.data()); const XPUType* v_data = reinterpret_cast(v.data()); XPUType* out_data = reinterpret_cast(out->data()); int64_t fa_layout = AttnQKVLayout_t::ATTN_BLHD; DenseTensor downstart_row_indices, upend_row_indices, downend_row_indices, upstart_row_indices; void *downstart_row_indices_data = nullptr, *upend_row_indices_data = nullptr, *downend_row_indices_data = nullptr, *upstart_row_indices_data = nullptr; bool is_flashmask = startend_row_indices.get_ptr() != nullptr; XPUStream flashmask_stream; if (is_flashmask) { xpu_stream_create(&flashmask_stream); PADDLE_ENFORCE_EQ( startend_row_indices->dims().size(), 4, common::errors::InvalidArgument( "flashmask_attention receive startend_row_indices with dim " "[batch_size, num_heads,seq_len, mask_bounds]")); PADDLE_ENFORCE_EQ(startend_row_indices->dims()[3] == 1 || startend_row_indices->dims()[3] == 2 || startend_row_indices->dims()[3] == 4, true, common::errors::InvalidArgument( "flashmask_attention startend_row_indices " "mask_bounds must in [1,2,4]")); downstart_row_indices = phi::Slice(dev_ctx, startend_row_indices.get(), {3}, {0}, {1}); downstart_row_indices_data = downstart_row_indices.data(); if (startend_row_indices->dims()[3] == 2) { if (!causal) { upend_row_indices = phi::Slice( dev_ctx, startend_row_indices.get(), {3}, {1}, {2}); upend_row_indices_data = upend_row_indices.data(); } else { downend_row_indices = phi::Slice( dev_ctx, startend_row_indices.get(), {3}, {1}, {2}); downend_row_indices_data = downend_row_indices.data(); } } else if (startend_row_indices->dims()[3] == 4) { upend_row_indices = phi::Slice( dev_ctx, startend_row_indices.get(), {3}, {3}, {4}); upend_row_indices_data = upend_row_indices.data(); downend_row_indices = phi::Slice( dev_ctx, startend_row_indices.get(), {3}, {1}, {2}); downend_row_indices_data = downend_row_indices.data(); upstart_row_indices = phi::Slice( dev_ctx, startend_row_indices.get(), {3}, {2}, {3}); upstart_row_indices_data = upstart_row_indices.data(); } } else { if (attn_mask.get_ptr() != nullptr) { const auto& mask_dims = attn_mask->dims(); if (mask_dims.size() == 3 || (mask_dims[1] == 1 && mask_dims.size() == 4)) { fa_layout |= AttnQKVLayout_t::BIAS_BLL; } else { PADDLE_ENFORCE_EQ(mask_dims.size(), 4, common::errors::InvalidArgument( "flash_attn_fwd requires mask's shape " "like [b,l,l] or [b, h, l, l]")); } if (!(attn_mask->dtype() == DataType::FLOAT32 || attn_mask->dtype() == DataType::FLOAT16 || attn_mask->dtype() == DataType::BFLOAT16)) { errors::Unimplemented( "Unsupported dtype for attention_mask in xpu flash attention, only " "float32, float16 and bfloat16 are supported."); } } } XPU_FA_DTYPE tgemm_dtype = get_flash_attn_tgemm(); XPU_FA_DTYPE taccum_dtype = get_flash_attn_taccum(); int r = 0; float* softmax_lse_data_fp32 = softmax_lse->data(); if (tgemm_dtype == XPU_FA_DTYPE::FA_FLOAT16 && taccum_dtype == XPU_FA_DTYPE::FA_FLOAT16) { // for taccum = fp16 XPUTypeFP16* softmax_lse_data = RAII_GUARD.alloc_l3_or_gm(softmax_lse->numel()); const XPUTypeFP16* bias_data = nullptr; if (!is_flashmask && attn_mask.get_ptr() != nullptr) { if (attn_mask->dtype() == DataType::FLOAT16) { bias_data = reinterpret_cast( attn_mask->data()); } else { // DataType::BFLOAT16 XPUTypeFP16* bias_tmp = RAII_GUARD.alloc_l3_or_gm(attn_mask->numel()); r = xpu::cast( dev_ctx.x_context(), reinterpret_cast(attn_mask->data()), bias_tmp, attn_mask->numel()); PADDLE_ENFORCE_XDNN_SUCCESS(r, "cast"); bias_data = bias_tmp; } } r = MHA_VARLEN_FWD(XPUType, XPUTypeFP16, XPUTypeFP16, int32_t); PADDLE_ENFORCE_XDNN_SUCCESS(r, "mha_varlen_fwd"); r = xpu::cast(dev_ctx.x_context(), softmax_lse_data, softmax_lse_data_fp32, softmax_lse->numel()); PADDLE_ENFORCE_XDNN_SUCCESS(r, "cast"); } else { float* softmax_lse_data = softmax_lse_data_fp32; const float* bias_data = nullptr; if (!is_flashmask && attn_mask.get_ptr() != nullptr) { float* bias_tmp = RAII_GUARD.alloc_l3_or_gm(attn_mask->numel()); r = xpu::cast( dev_ctx.x_context(), reinterpret_cast(attn_mask->data()), bias_tmp, attn_mask->numel()); PADDLE_ENFORCE_XDNN_SUCCESS(r, "cast"); bias_data = bias_tmp; } if (tgemm_dtype == XPU_FA_DTYPE::FA_FLOAT16) { r = MHA_VARLEN_FWD(XPUType, float, XPUTypeFP16, int32_t); } else if (tgemm_dtype == XPU_FA_DTYPE::FA_FLOAT) { r = MHA_VARLEN_FWD(XPUType, float, float, int32_t); } else { r = MHA_VARLEN_FWD(XPUType, float, tfloat32, int32_t); } PADDLE_ENFORCE_XDNN_SUCCESS(r, "mha_varlen_fwd"); } if (is_flashmask && flashmask_stream != nullptr) { int r = xpu_wait(flashmask_stream); PADDLE_ENFORCE_XPU_SUCCESS(r); xpu_stream_destroy(flashmask_stream); } } template void FlashAttnUnpaddedKernel(const Context& dev_ctx, const DenseTensor& q, const DenseTensor& k, const DenseTensor& v, const DenseTensor& cu_seqlens_q, const DenseTensor& cu_seqlens_k, const optional& fixed_seed_offset, const optional& attn_mask, const Scalar& max_seqlen_q, const Scalar& max_seqlen_k, float scale, float dropout, bool causal, bool return_softmax, bool is_test, const std::string& rng_name, DenseTensor* out, DenseTensor* softmax, DenseTensor* softmax_lse, DenseTensor* seed_offset) { // q, k, v [batch_size * seq_len, num_heads, head_dim] std::vector dims = vectorize(q.dims()); const int64_t batch_size = cu_seqlens_q.numel() - 1; const int64_t num_heads = dims[1]; const int64_t head_size = dims[2]; const int64_t num_heads_k = k.dims()[1]; const int64_t head_size_v = v.dims()[2]; api::VectorParam qlod{cu_seqlens_q.data(), static_cast(cu_seqlens_q.numel()), nullptr}; api::VectorParam kvlod{cu_seqlens_k.data(), static_cast(cu_seqlens_k.numel()), nullptr}; FlashAttnKernelBase(dev_ctx, q, k, v, qlod, kvlod, fixed_seed_offset, attn_mask, paddle::none, batch_size, max_seqlen_q, max_seqlen_k, num_heads, num_heads_k, head_size, head_size_v, scale, dropout, causal, return_softmax, is_test, rng_name, out, softmax, softmax_lse, seed_offset); } template void FlashAttnKernel(const Context& dev_ctx, const DenseTensor& q, const DenseTensor& k, const DenseTensor& v, const optional& fixed_seed_offset, const optional& attn_mask, float dropout, bool causal, bool return_softmax, bool is_test, const std::string& rng_name, DenseTensor* out, DenseTensor* softmax, DenseTensor* softmax_lse, DenseTensor* seed_offset) { if (return_softmax == true) { PADDLE_THROW( common::errors::Unimplemented("return_softmax should be false")); } // q, k, v [batch_size, seq_len, num_heads, head_dim] const auto& dims = q.dims(); PADDLE_ENFORCE_EQ(dims.size(), 4, common::errors::InvalidArgument( "flash_attn receive input with dim " "[batch_size, seq_len, num_heads, head_dim]")); PADDLE_ENFORCE_EQ(k.dims().size(), 4, common::errors::InvalidArgument( "flash_attn receive input with dim " "[batch_size, seq_len, num_heads, head_dim]")); PADDLE_ENFORCE_EQ(v.dims().size(), 4, common::errors::InvalidArgument( "flash_attn receive input with dim " "[batch_size, seq_len, num_heads, head_dim]")); PADDLE_ENFORCE_EQ(out->dims().size(), 4, common::errors::InvalidArgument( "flash_attn receive input with dim " "[batch_size, seq_len, num_heads, head_dim]")); const int64_t batch_size = dims[0]; const int64_t seqlen_q = dims[1]; const int64_t num_heads = dims[2]; const int64_t head_size = dims[3]; const int64_t seqlen_k = k.dims()[1]; const int64_t num_heads_k = k.dims()[2]; const int64_t head_size_v = v.dims()[3]; // lod info std::vector qlod_vec = {0}; std::vector kvlod_vec = {0}; for (int batch_idx = 1; batch_idx <= batch_size; ++batch_idx) { qlod_vec.push_back(seqlen_q * batch_idx); kvlod_vec.push_back(seqlen_k * batch_idx); } api::VectorParam qlod{ qlod_vec.data(), static_cast(qlod_vec.size()), nullptr}; api::VectorParam kvlod{ kvlod_vec.data(), static_cast(kvlod_vec.size()), nullptr}; FlashAttnKernelBase(dev_ctx, q, k, v, qlod, kvlod, fixed_seed_offset, attn_mask, paddle::none, batch_size, seqlen_q, seqlen_k, num_heads, num_heads_k, head_size, head_size_v, 0.0, // scale dropout, causal, return_softmax, is_test, rng_name, out, softmax, softmax_lse, seed_offset); } template void FlashMaskKernel(const Context& dev_ctx, const DenseTensor& q, const DenseTensor& k, const DenseTensor& v, const DenseTensor& startend_row_indices, const optional& fixed_seed_offset, float dropout, bool causal, bool return_softmax, bool is_test, const std::string& rng_name, DenseTensor* out, DenseTensor* softmax, DenseTensor* softmax_lse, DenseTensor* seed_offset) { if (return_softmax == true) { PADDLE_THROW( common::errors::Unimplemented("return_softmax should be false")); } // Handle 0-size tensors: return zeros without calling XPU kernel // to avoid invalid memory access if (q.numel() == 0 || k.numel() == 0 || v.numel() == 0) { if (out) { Full(dev_ctx, out->dims(), 0, out); } if (softmax) { Full(dev_ctx, softmax->dims(), 0, softmax); } if (softmax_lse) { Full(dev_ctx, softmax_lse->dims(), 0, softmax_lse); } if (seed_offset) { Full(dev_ctx, seed_offset->dims(), 0, seed_offset); } return; } // q, k, v [batch_size, seq_len, num_heads, head_dim] const auto& dims = q.dims(); PADDLE_ENFORCE_EQ(dims.size(), 4, common::errors::InvalidArgument( "flash_attn receive input with dim " "[batch_size, seq_len, num_heads, head_dim]")); const int64_t batch_size = dims[0]; const int64_t seqlen_q = dims[1]; const int64_t num_heads = dims[2]; const int64_t head_size = dims[3]; const int64_t seqlen_k = k.dims()[1]; const int64_t num_heads_k = k.dims()[2]; const int64_t head_size_v = v.dims()[3]; // lod info std::vector qlod_vec = {0}; std::vector kvlod_vec = {0}; for (int batch_idx = 1; batch_idx <= batch_size; ++batch_idx) { qlod_vec.push_back(seqlen_q * batch_idx); kvlod_vec.push_back(seqlen_k * batch_idx); } api::VectorParam qlod{ qlod_vec.data(), static_cast(qlod_vec.size()), nullptr}; api::VectorParam kvlod{ kvlod_vec.data(), static_cast(kvlod_vec.size()), nullptr}; FlashAttnKernelBase(dev_ctx, q, k, v, qlod, kvlod, fixed_seed_offset, paddle::none, startend_row_indices, batch_size, seqlen_q, seqlen_k, num_heads, num_heads_k, head_size, head_size_v, 0.0, // scale dropout, causal, return_softmax, is_test, rng_name, out, softmax, softmax_lse, seed_offset); } } // namespace phi PD_REGISTER_KERNEL(flash_attn_unpadded, XPU, ALL_LAYOUT, phi::FlashAttnUnpaddedKernel, float, phi::float16, phi::bfloat16) { kernel->InputAt(3).SetBackend(phi::Backend::CPU); // cu_seqlens_q kernel->InputAt(4).SetBackend(phi::Backend::CPU); // cu_seqlens_k kernel->InputAt(5).SetBackend( phi::Backend::ALL_BACKEND); // fixed_seed_offset } PD_REGISTER_KERNEL(flash_attn, XPU, ALL_LAYOUT, phi::FlashAttnKernel, phi::bfloat16, float, phi::float16) { kernel->InputAt(3).SetBackend( phi::Backend::ALL_BACKEND); // fixed_seed_offset } PD_REGISTER_KERNEL(flashmask_attention, XPU, ALL_LAYOUT, phi::FlashMaskKernel, phi::float16, phi::bfloat16) { kernel->InputAt(4).SetBackend( phi::Backend::ALL_BACKEND); // fixed_seed_offset }