// Copyright (c) 2025 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 #include "glog/logging.h" // For VLOG() #include "paddle/common/enforce.h" #include "paddle/common/errors.h" #include "paddle/common/flags.h" #include "paddle/phi/common/data_type.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/core/platform/device_context.h" #include "paddle/phi/core/tensor_utils.h" #include "paddle/phi/core/utils/data_type.h" #include "paddle/phi/kernels/empty_kernel.h" #include "paddle/phi/kernels/funcs/elementwise_base.h" #include "paddle/phi/kernels/slice_kernel.h" #include "paddle/utils/none.h" #include "paddle/phi/kernels/gpu/flash_attn_utils.h" #include "paddle/phi/kernels/gpu/flash_attn_v3_utils.h" #include "paddle/phi/kernels/gpu/flash_attn_v3_kernel.h" namespace phi { template void FlashAttnV3BaseKernel( const Context &dev_ctx, const DenseTensor &q, const DenseTensor &k, const DenseTensor &v, const optional &k_new_, // (b, s_k_new, h_k, d) or (total_k_new, h_k, d) if there is // cu_seqlens_k_new const optional &v_new_, // (b, s_k_new, h_k, dv) or (total_k_new, h_k, dv) if there is // cu_seqlens_k_new const optional &q_v_, // (b, s_q, h, dv) or (total_q_new, h, // dv) if there is cu_seqlens_q const optional &out_, // (b, s_q, h, dv) or (total_q, h, dv) if there is cu_seqlens_q const optional &cu_seqlens_q_, // b+1 const optional &cu_seqlens_k_, // b+1 const optional &cu_seqlens_k_new_, // b+1 const optional &seqused_q_, // b. If given, only this many elements of each batch // element's queries and outputs are used. const optional &seqused_k_, // b. If given, only this many elements of each batch // element's keys are used. const optional &page_table_, // (b_k, max_num_pages_per_seq) const optional &kv_batch_idx_, // b. indices to index into the KV cache const optional &leftpad_k_, // b const optional &rotary_cos_, // seqlen_ro x (rotary_dim / 2) const optional &rotary_sin_, // seqlen_ro x (rotary_dim / 2) const optional &q_descale_, // (b, h_k), not (b, h) const optional &k_descale_, // (b, h_k) const optional &v_descale_, // (b, h_k) const optional &scheduler_metadata_, // (b + 1) const int max_seqlen_q_, // if max_seqlen_q_ is set to 0, it indicates that it is // uninitialized and should not be referenced // TODO(tridao): check if we need max_seqlen_k const int max_seqlen_k_, // if max_seqlen_q_ is set to 0, it indicates that it is // uninitialized and should not be referenced const float softmax_scale, bool is_causal, int window_size_left, int window_size_right, const float softcap, const bool is_rotary_interleaved, // if true, rotary combines indices 0 & // 1, else indices 0 & rotary_dim / 2 int num_splits, const bool manual_set_pack_gqa, const bool pack_gqa_, // the pack_gqa_ will be used only if manual_set_pack_gqa is // set to True; otherwise, the internal heuristic // get_pack_gqa() from fa3 will decide whether to pack gqa const int sm_margin, DenseTensor *out, DenseTensor *softmax_lse, DenseTensor *out_accum, DenseTensor *softmax_lse_accum) { #ifdef PADDLE_WITH_FLASHATTN_V3 // TODO(umiswing): support ampere int device_id = dev_ctx.GetPlace().GetDeviceId(); auto dprops = paddle::platform::GetDeviceProperties(device_id); const bool is_sm90 = dprops.major == 9 && dprops.minor == 0; PADDLE_ENFORCE_EQ(is_sm90, true, common::errors::Unavailable( "FlashAttention-3 only supports Hopper GPUs.")); auto q_type = q.dtype(); PADDLE_ENFORCE_EQ( (q_type == DataType::FLOAT16 || q_type == DataType::BFLOAT16 || q_type == DataType::FLOAT8_E4M3FN), true, common::errors::InvalidArgument( "FlashAttention-3 only supports fp16, bf16, and fp8_e4m3 data type")); PADDLE_ENFORCE_EQ(k.dtype(), q_type, common::errors::InvalidArgument( "query and key must have the same dtype")); PADDLE_ENFORCE_EQ(v.dtype(), q_type, common::errors::InvalidArgument( "query and value must have the same dtype")); CHECK_DEVICE(q); CHECK_DEVICE(k); CHECK_DEVICE(v); PADDLE_ENFORCE_EQ(q.strides()[q.strides().size() - 1], 1, common::errors::InvalidArgument( "Input tensor must have contiguous last dimension")); PADDLE_ENFORCE_EQ(k.strides()[k.strides().size() - 1], 1, common::errors::InvalidArgument( "Input tensor must have contiguous last dimension")); PADDLE_ENFORCE_EQ(v.strides()[v.strides().size() - 1], 1, common::errors::InvalidArgument( "Input tensor must have contiguous last dimension")); DenseTensor page_table; // const bool paged_KV = page_table_.has_value(); // umiswing: this is stupid but idk how to use optional const bool paged_KV = page_table_.is_initialized(); if (paged_KV) { page_table = page_table_.get(); CHECK_DEVICE(page_table); PADDLE_ENFORCE_EQ(page_table.dtype(), DataType::INT32, common::errors::InvalidArgument( "page_table must have dtype paddle.int32")); PADDLE_ENFORCE_EQ(page_table.strides()[page_table.strides().size() - 1], 1, common::errors::InvalidArgument( "page_table must have contiguous last dimension")); } // TODO(umiswing): support cusum DenseTensor cu_seqlens_q; // bool const is_varlen_q = cu_seqlens_q_.has_value(); // TODO(umiswing): this is stupid, must fix it (after understand // optional) const bool is_varlen_q = cu_seqlens_q_.is_initialized(); if (is_varlen_q) { cu_seqlens_q = cu_seqlens_q_.get(); CHECK_DEVICE(cu_seqlens_q); CHECK_CONTIGUOUS(cu_seqlens_q); PADDLE_ENFORCE_EQ(cu_seqlens_q.dtype(), DataType::INT32, common::errors::InvalidArgument( "cu_seqlens_q must have dtype paddle.int32")); PADDLE_ENFORCE_NE( max_seqlen_q_, 0, common::errors::InvalidArgument( "max_seqlen_q must be provided if cu_seqlens_q is provided")); } DenseTensor cu_seqlens_k; const bool is_varlen_k = cu_seqlens_k_.is_initialized(); if (is_varlen_k) { cu_seqlens_k = cu_seqlens_k_.get(); CHECK_DEVICE(cu_seqlens_k); CHECK_CONTIGUOUS(cu_seqlens_k); PADDLE_ENFORCE_EQ(cu_seqlens_k.dtype(), DataType::INT32, common::errors::InvalidArgument( "cu_seqlens_k must have dtype paddle.int32")); PADDLE_ENFORCE_NE( max_seqlen_k_, 0, common::errors::InvalidArgument( "max_seqlen_k must be provided if cu_seqlens_k is provided")); PADDLE_ENFORCE_EQ( !paged_KV, true, common::errors::InvalidArgument( "If cu_seqlens_k is passed in, then page table is not supported")); PADDLE_ENFORCE_EQ( !kv_batch_idx_, true, common::errors::InvalidArgument( "If cu_seqlens_k is passed in, then page table is not supported")); } auto const sizes = q.dims(); const int batch_size = !is_varlen_q ? sizes[0] : cu_seqlens_q.dims()[0] - 1; int seqlen_q = !is_varlen_q ? sizes[1] : max_seqlen_q_; int total_q = !is_varlen_q ? batch_size * sizes[1] : sizes[0]; int64_t num_heads = q.dims()[q.dims().size() - 2]; int64_t const head_size = q.dims()[q.dims().size() - 1]; int const head_size_v = v.dims()[v.dims().size() - 1]; int const max_num_pages_per_seq = !paged_KV ? 0 : page_table.dims()[1]; int const num_pages = !paged_KV ? 0 : k.dims()[0]; int const page_size = !paged_KV ? 1 : k.dims()[1]; int const seqlen_k = !is_varlen_k ? (!paged_KV ? k.dims()[1] : max_num_pages_per_seq * page_size) : max_seqlen_k_; int const total_k = !is_varlen_k ? batch_size * k.dims()[1] : k.dims()[0]; int const num_heads_k = k.dims()[k.dims().size() - 2]; int const batch_size_k = !paged_KV ? (!is_varlen_k ? k.dims()[0] : cu_seqlens_k.dims()[0] - 1) : page_table.dims()[0]; if (!kv_batch_idx_.is_initialized()) { PADDLE_ENFORCE_EQ(batch_size, batch_size_k, common::errors::InvalidArgument( "batch_size must be equal to batch_size_k")); } int const max_headdim = get_max_headdim(); PADDLE_ENFORCE_LE( head_size, max_headdim, common::errors::InvalidArgument( "FlashAttention forward only supports head dimension at most %d", max_headdim)); PADDLE_ENFORCE_EQ( num_heads % num_heads_k, 0, common::errors::InvalidArgument( "Number of heads in key/value must divide number of heads in query")); if (head_size_v != head_size) { PADDLE_ENFORCE_EQ( ((head_size > 128 && head_size <= 192 && head_size_v > 96 && head_size_v <= 128) || (head_size <= 64 && head_size_v <= 512)), true, common::errors::InvalidArgument( "If V headdim is different from Q/K dim, we only support " "Q/K headdim in (128, 192] and V headdim in (96, 128], " "or (Q/K <= 64 and V <= 512).")); PADDLE_ENFORCE_EQ(dprops.major, 9, common::errors::InvalidArgument( "Only Hopper supports different V headdim")); if (head_size_v > 256) { PADDLE_ENFORCE_EQ( (q_type == DataType::FLOAT16 || q_type == DataType::BFLOAT16), true, common::errors::InvalidArgument( "HeaddimV > 256 requires fp16 and bf16 data type")); } } // This needs to go before kBlockM & kBlockN since we rely on the correct // window_size and is_causal to set kBlockM // TODO(tridao): check this if (window_size_left >= seqlen_k - 1) { window_size_left = -1; } if (window_size_right >= seqlen_q - 1) { window_size_right = -1; } // causal=true is the same as causal=false in this case if (seqlen_q == 1 && window_size_left == -1 && window_size_right == -1) { // Special case of hdim 128 where we want causal to have kBlockN=128, better // for pagedKV and TMA if ((head_size <= 64 || head_size > 128) || !paged_KV) { is_causal = false; } } if (is_causal) { window_size_right = 0; } // There's a case where is_causal=false, window_size=(-1, 0). Then // set_params_fprop will set params.is_causal=true. If we don't have is_causal // here matching params.is_causal, we might get the wrong kBlockM. is_causal = window_size_left < 0 && window_size_right == 0; if (!is_varlen_q) { CHECK_SHAPE(q, batch_size, seqlen_q, num_heads, head_size); } else { CHECK_SHAPE(q, total_q, num_heads, head_size); CHECK_SHAPE(cu_seqlens_q, batch_size + 1); } if (!paged_KV) { if (!is_varlen_k) { CHECK_SHAPE(k, batch_size_k, seqlen_k, num_heads_k, head_size); CHECK_SHAPE(v, batch_size_k, seqlen_k, num_heads_k, head_size_v); } else { CHECK_SHAPE(k, total_k, num_heads_k, head_size); CHECK_SHAPE(v, total_k, num_heads_k, head_size_v); CHECK_SHAPE(cu_seqlens_k, batch_size + 1); } } else { CHECK_SHAPE(k, num_pages, page_size, num_heads_k, head_size); CHECK_SHAPE(v, num_pages, page_size, num_heads_k, head_size_v); CHECK_SHAPE(page_table, batch_size_k, max_num_pages_per_seq); } if (seqused_q_.is_initialized()) { auto seqused_q = seqused_q_.get(); PADDLE_ENFORCE_EQ( seqused_q.dtype(), DataType::INT32, common::errors::InvalidArgument("seqused_q must have dtype int32")); CHECK_DEVICE(seqused_q); CHECK_CONTIGUOUS(seqused_q); CHECK_SHAPE(seqused_q, batch_size); } if (seqused_k_.is_initialized()) { auto seqused_k = seqused_k_.get(); PADDLE_ENFORCE_EQ( seqused_k.dtype(), DataType::INT32, common::errors::InvalidArgument("seqused_k must have dtype int32")); CHECK_DEVICE(seqused_k); CHECK_CONTIGUOUS(seqused_k); CHECK_SHAPE(seqused_k, batch_size); } if (leftpad_k_.is_initialized()) { auto leftpad_k = leftpad_k_.get(); PADDLE_ENFORCE_EQ( leftpad_k.dtype(), DataType::INT32, common::errors::InvalidArgument("leftpad_k must have dtype int32")); CHECK_DEVICE(leftpad_k); CHECK_CONTIGUOUS(leftpad_k); CHECK_SHAPE(leftpad_k, batch_size); } // This is what we will template on bool const is_varlen = is_varlen_q || is_varlen_k || seqused_q_.is_initialized() || seqused_k_.is_initialized() || leftpad_k_.is_initialized(); #ifdef FLASHATTENTION_DISABLE_VARLEN PADDLE_ENFORCE_EQ(!is_varlen, true, common::errors::Unavailable( "This flash attention build does not support varlen.")); #endif int const alignment = q_type == DataType::FLOAT8_E4M3FN ? 16 : 8; PADDLE_ENFORCE_EQ(head_size % alignment, 0, common::errors::InvalidArgument( "head_size should be a multiple of %d", alignment)); PADDLE_ENFORCE_EQ(head_size_v % alignment, 0, common::errors::InvalidArgument( "head_size_v should be a multiple of %d", alignment)); auto out_type = q_type == DataType::FLOAT8_E4M3FN ? DataType::BFLOAT16 : q_type; if (out_.is_initialized()) { *out = out_.get(); PADDLE_ENFORCE_EQ( out->dtype(), out_type, common::errors::InvalidArgument( "For FP16/BF16 input, output must have the same dtype as " "inputs. For FP8 input, output must have dtype BF16")); CHECK_DEVICE((*out)); PADDLE_ENFORCE_EQ(out->strides()[out->strides().size() - 1], 1, common::errors::InvalidArgument( "Output tensor must have contiguous last dimension")); if (!is_varlen_q) { CHECK_SHAPE((*out), batch_size, seqlen_q, num_heads, head_size_v); } else { CHECK_SHAPE((*out), total_q, num_heads, head_size_v); } } else { if (!is_varlen_q) { out->Resize({batch_size, seqlen_q, num_heads, head_size_v}); } else { out->Resize({total_q, num_heads, head_size_v}); } if (q_type == DataType::FLOAT8_E4M3FN) { dev_ctx.template Alloc(out); } else { // umiswing: assuming T is Input Type dev_ctx.template Alloc(out); } } auto round_multiple = [](int x, int m) { return (x + m - 1) / m * m; }; int const head_size_rounded = round_up_headdim(head_size); int const head_size_v_rounded = round_up_headdim(head_size_v); int const seqlen_q_rounded = round_multiple(seqlen_q, 128); int const seqlen_k_rounded = round_multiple(seqlen_k, 128); if (!is_varlen_q) { softmax_lse->Resize({batch_size, num_heads, seqlen_q}); } else { softmax_lse->Resize({num_heads, total_q}); } dev_ctx.template Alloc(softmax_lse); Flash_fwd_params *params_handle = get_flash_fwd_params_handle(); dynload::fa3_clear_fwd_params_handle(params_handle); set_params_fprop( params_handle, batch_size, seqlen_q, seqlen_k, seqlen_q_rounded, seqlen_k_rounded, num_heads, num_heads_k, head_size, head_size_rounded, q, k, v, out, !is_varlen_q ? nullptr : cu_seqlens_q.data(), !is_varlen_k ? nullptr : cu_seqlens_k.data(), seqused_q_.is_initialized() ? const_cast(seqused_q_.get().data()) : nullptr, seqused_k_.is_initialized() ? const_cast(seqused_k_.get().data()) : nullptr, softmax_lse->data(), /*p_dropout=*/0.f, softmax_scale, window_size_left, window_size_right, dprops, softcap, sm_margin); dynload::fa3_fwd_params_set_total_q(params_handle, total_q); dynload::fa3_fwd_params_set_total_k(params_handle, total_k); dynload::fa3_fwd_params_set_b_k(params_handle, batch_size_k); dynload::fa3_fwd_params_set_dv(params_handle, head_size_v); dynload::fa3_fwd_params_set_dv_rounded(params_handle, head_size_v_rounded); if (leftpad_k_ .is_initialized()) { // This needs to be set before get_pagedkv_tma dynload::fa3_fwd_params_set_leftpad_k(params_handle, leftpad_k_.get().data()); } if (paged_KV) { dynload::fa3_fwd_params_set_page_table(params_handle, page_table.data()); dynload::fa3_fwd_params_set_page_table_batch_stride( params_handle, page_table.strides()[0]); } dynload::fa3_fwd_params_set_page_size(params_handle, page_size); dynload::fa3_fwd_params_set_num_pages(params_handle, num_pages); if (k_new_.is_initialized()) { // This needs to be set before get_pagedkv_tma DenseTensor k_new, v_new; PADDLE_ENFORCE_EQ( v_new_.is_initialized(), true, common::errors::InvalidArgument( "If k_new is supplied, v_new must also be passed in")); PADDLE_ENFORCE_EQ( seqused_k_.is_initialized(), true, common::errors::InvalidArgument( "If k_new is supplied, seqlens_k must also be passed in")); PADDLE_ENFORCE_LE( seqlen_q, seqlen_k, common::errors::InvalidArgument( "If k_new is supplied, it must have seqlen <= the seqlen " "of the KV cache")); DenseTensor cu_seqlens_k_new; bool const is_varlen_k_new = cu_seqlens_k_new_.is_initialized(); if (is_varlen_k_new) { cu_seqlens_k_new = cu_seqlens_k_new_.get(); CHECK_DEVICE(cu_seqlens_k_new); CHECK_CONTIGUOUS(cu_seqlens_k_new); PADDLE_ENFORCE_EQ(cu_seqlens_k_new.dtype(), DataType::INT32, common::errors::InvalidArgument( "cu_seqlens_k_new must have dtype paddle.int32")); } k_new = k_new_.get(); v_new = v_new_.get(); PADDLE_ENFORCE_EQ(k_new.dtype(), q_type, common::errors::InvalidArgument( "k_new must have the same dtype as query")); PADDLE_ENFORCE_EQ(v_new.dtype(), q_type, common::errors::InvalidArgument( "v_new must have the same dtype as query")); CHECK_DEVICE(k_new); CHECK_DEVICE(v_new); PADDLE_ENFORCE_EQ(k_new.strides()[k_new.strides().size() - 1], 1, common::errors::InvalidArgument( "k_new tensor must have contiguous last dimension")); PADDLE_ENFORCE_EQ(v_new.strides()[v_new.strides().size() - 1], 1, common::errors::InvalidArgument( "v_new tensor must have contiguous last dimension")); // We don't need max_seqlen_k_new, so seqlen_k_new can be whatever when // is_varlen_k_new int seqlen_k_new = !is_varlen_k_new ? k_new.dims()[1] : 0; int total_k_new = !is_varlen_k_new ? batch_size * k_new.dims()[1] : k_new.dims()[0]; if (!is_varlen_k_new) { CHECK_SHAPE(k_new, batch_size, seqlen_k_new, num_heads_k, head_size); CHECK_SHAPE(v_new, batch_size, seqlen_k_new, num_heads_k, head_size_v); } else { CHECK_SHAPE(k_new, total_k_new, num_heads_k, head_size); CHECK_SHAPE(v_new, total_k_new, num_heads_k, head_size_v); CHECK_SHAPE(cu_seqlens_k_new, batch_size + 1); } // umiswing: dump this to shared library dynload::fa3_fwd_params_set_seqlen_knew(params_handle, seqlen_k_new); dynload::fa3_fwd_params_set_total_knew(params_handle, total_k_new); dynload::fa3_fwd_params_set_knew_ptr(params_handle, const_cast(k_new.data())); dynload::fa3_fwd_params_set_vnew_ptr(params_handle, const_cast(v_new.data())); // All stride are in elements, not bytes. dynload::fa3_fwd_params_set_knew_row_stride( params_handle, k_new.strides()[k_new.strides().size() - 3]); dynload::fa3_fwd_params_set_vnew_row_stride( params_handle, v_new.strides()[v_new.strides().size() - 3]); dynload::fa3_fwd_params_set_knew_head_stride( params_handle, k_new.strides()[k_new.strides().size() - 2]); dynload::fa3_fwd_params_set_vnew_head_stride( params_handle, v_new.strides()[v_new.strides().size() - 2]); if (!is_varlen_k_new) { dynload::fa3_fwd_params_set_knew_batch_stride(params_handle, k_new.strides()[0]); dynload::fa3_fwd_params_set_vnew_batch_stride(params_handle, v_new.strides()[0]); } if (is_varlen_k_new) { dynload::fa3_fwd_params_set_cu_seqlens_knew(params_handle, cu_seqlens_k_new.data()); } } // 992 = 32 * 31 is the max supported batch in prepare_varlen_num_blocks // kernel bool const use_dynamic_split = is_varlen && dynload::fa3_fwd_params_get_b(params_handle) <= 992; // Temporarily set num_splits_dynamic_ptr to 1 since get_num_splits checks it dynload::fa3_fwd_params_set_num_splits_dynamic_ptr( params_handle, !use_dynamic_split ? nullptr : reinterpret_cast(1)); dynload::fa3_fwd_params_set_pagedkv_tma( params_handle, dynload::fa3_get_pagedkv_tma(params_handle)); if (num_splits <= 0) { num_splits = dynload::fa3_get_num_splits(params_handle); } dynload::fa3_fwd_params_set_num_splits(params_handle, num_splits); // Always enable PackGQA for Split, and get_pack_gqa requires // params.num_splits to decide const bool pack_gqa = manual_set_pack_gqa ? pack_gqa_ : dynload::fa3_get_pack_gqa(params_handle); dynload::fa3_fwd_params_set_pack_gqa(params_handle, pack_gqa); // This needs to be set after get_num_splits DenseTensor tile_count_semaphore; // Contains the semaphore and optionally // num_splits_dynamic // We don't use the persistent scheduler if Split and not Varlen const bool params_is_causal = dynload::fa3_fwd_params_get_is_causal(params_handle); const bool params_is_local = dynload::fa3_fwd_params_get_is_local(params_handle); const int params_num_splits = dynload::fa3_fwd_params_get_num_splits(params_handle); const int params_b = dynload::fa3_fwd_params_get_b(params_handle); const int params_arch = dynload::fa3_fwd_params_get_arch(params_handle); bool const scheduler_needs_semaphore = params_arch >= 90 ? (((params_is_causal || params_is_local) && (params_num_splits == 1)) || is_varlen) : ((params_is_causal && !is_varlen) || (is_varlen && params_num_splits > 1)); if (scheduler_needs_semaphore || use_dynamic_split) { int metadata_size = static_cast(scheduler_needs_semaphore) + static_cast(use_dynamic_split) * params_b; dynload::fa3_fwd_params_set_skip_scheduler_metadata_computation( params_handle, scheduler_metadata_.is_initialized()); if (scheduler_metadata_.is_initialized()) { DenseTensor scheduler_metadata = scheduler_metadata_.get(); CHECK_DEVICE(scheduler_metadata); CHECK_SHAPE(scheduler_metadata, metadata_size); CHECK_CONTIGUOUS(scheduler_metadata); PADDLE_ENFORCE_EQ(scheduler_metadata.dtype(), DataType::INT32, common::errors::InvalidArgument( "scheduler_metadata must have dtype int32")); tile_count_semaphore = scheduler_metadata; } else { tile_count_semaphore = Empty(dev_ctx, {metadata_size}); } if (scheduler_needs_semaphore && !use_dynamic_split) { funcs::SetConstant set_zero; set_zero(dev_ctx, &tile_count_semaphore, int32_t{0}); // If varlen we'll manually do the zero-ing } dynload::fa3_fwd_params_set_tile_count_semaphore( params_handle, scheduler_needs_semaphore ? const_cast(tile_count_semaphore.data()) : nullptr); dynload::fa3_fwd_params_set_num_splits_dynamic_ptr( params_handle, use_dynamic_split ? const_cast(tile_count_semaphore.data()) + 1 : nullptr); } if (q_v_.is_initialized()) { PADDLE_ENFORCE_LT(head_size, 64, common::errors::InvalidArgument( "q_v is only supported for head_size <= 64")); PADDLE_ENFORCE_EQ( (q_type == DataType::FLOAT16 || q_type == DataType::FLOAT16), true, common::errors::InvalidArgument( "q_v is only supported for fp16 and bf16 data type")); PADDLE_ENFORCE_EQ(params_arch, 90, common::errors::InvalidArgument( "q_v is only supported for Hopper GPUs")); DenseTensor q_v = q_v_.get(); PADDLE_ENFORCE_EQ(q_v.dtype(), q_type, common::errors::InvalidArgument( "q_v must have the same dtype as query")); CHECK_DEVICE(q_v); PADDLE_ENFORCE_EQ(q_v.strides()[q_v.strides().size() - 1], 1, common::errors::InvalidArgument( "q_v tensor must have contiguous last dimension")); if (!is_varlen_q) { CHECK_SHAPE(q_v, batch_size, seqlen_q, num_heads, head_size_v); } else { CHECK_SHAPE(q_v, total_q, num_heads, head_size_v); } dynload::fa3_fwd_params_set_qv_ptr(params_handle, const_cast(q_v.data())); // All stride are in elements, not bytes. dynload::fa3_fwd_params_set_qv_row_stride( params_handle, q_v.strides()[q_v.strides().size() - 3]); dynload::fa3_fwd_params_set_qv_head_stride( params_handle, q_v.strides()[q_v.strides().size() - 2]); if (!is_varlen_q) { dynload::fa3_fwd_params_set_qv_batch_stride(params_handle, q_v.strides()[0]); } } if (rotary_cos_.is_initialized()) { PADDLE_ENFORCE_EQ( k_new_.is_initialized(), true, common::errors::InvalidArgument( "If rotary cos/sin are provided, new key / value to be " "appended to KV cache must also be provided")); DenseTensor rotary_cos = rotary_cos_.get(); CHECK_DEVICE(rotary_cos); CHECK_CONTIGUOUS(rotary_cos); int params_rotary_dim = rotary_cos.dims()[1] * 2; dynload::fa3_fwd_params_set_rotary_dim(params_handle, params_rotary_dim); PADDLE_ENFORCE_LE( params_rotary_dim, head_size, common::errors::InvalidArgument("rotary_dim must be <= headdim")); PADDLE_ENFORCE_EQ( params_rotary_dim % 16, 0, common::errors::InvalidArgument( "Only rotary dimensions divisible by 16 are currently supported")); // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t seqlen_ro = rotary_cos.dims()[0]; if (paged_KV) { PADDLE_ENFORCE_GE( seqlen_ro, seqlen_k, common::errors::InvalidArgument( "cos/sin seqlen must be at least the seqlen of KV cache")); } CHECK_SHAPE(rotary_cos, seqlen_ro, params_rotary_dim / 2); PADDLE_ENFORCE_EQ(rotary_cos.dtype(), q_type, common::errors::InvalidArgument( "rotary_cos must have the same dtype as query")); PADDLE_ENFORCE_EQ( rotary_sin_.is_initialized(), true, common::errors::InvalidArgument( "If rotary cos is provided, rotary sin must also be provided")); auto rotary_sin = rotary_sin_.get(); CHECK_DEVICE(rotary_sin); CHECK_CONTIGUOUS(rotary_sin); CHECK_SHAPE(rotary_sin, seqlen_ro, params_rotary_dim / 2); PADDLE_ENFORCE_EQ(rotary_sin.dtype(), q_type, common::errors::InvalidArgument( "rotary_cos must have the same dtype as query")); dynload::fa3_fwd_params_set_rotary_cos_ptr( params_handle, const_cast(rotary_cos.data())); dynload::fa3_fwd_params_set_rotary_sin_ptr( params_handle, const_cast(rotary_sin.data())); dynload::fa3_fwd_params_set_is_rotary_interleaved(params_handle, is_rotary_interleaved); } else { dynload::fa3_fwd_params_set_rotary_dim(params_handle, 0); } if (kv_batch_idx_.is_initialized()) { DenseTensor kv_batch_idx = kv_batch_idx_.get(); CHECK_DEVICE(kv_batch_idx); CHECK_CONTIGUOUS(kv_batch_idx); PADDLE_ENFORCE_EQ( kv_batch_idx.dtype(), DataType::INT32, common::errors::InvalidArgument("kv_batch_idx must have dtype int32")); dynload::fa3_fwd_params_set_kv_batch_idx( params_handle, reinterpret_cast(kv_batch_idx.data())); } if (dynload::fa3_fwd_params_get_num_splits(params_handle) > 1) { PADDLE_ENFORCE_LE( dynload::fa3_fwd_params_get_num_splits(params_handle), 256, common::errors::InvalidArgument("num_splits > 256 not supported")); if (!is_varlen_q) { out_accum->Resize( make_ddim({dynload::fa3_fwd_params_get_num_splits(params_handle), batch_size, num_heads, seqlen_q, head_size_v})); softmax_lse_accum->Resize( make_ddim({dynload::fa3_fwd_params_get_num_splits(params_handle), batch_size, num_heads, seqlen_q})); dev_ctx.template Alloc(out_accum); dev_ctx.template Alloc(softmax_lse_accum); dynload::fa3_fwd_params_set_oaccum_batch_stride(params_handle, out_accum->strides()[1]); dynload::fa3_fwd_params_set_lseaccum_batch_stride( params_handle, softmax_lse_accum->strides()[1]); } else { out_accum->Resize( make_ddim({dynload::fa3_fwd_params_get_num_splits(params_handle), num_heads, total_q, head_size_v})); softmax_lse_accum->Resize( make_ddim({dynload::fa3_fwd_params_get_num_splits(params_handle), num_heads, total_q})); dev_ctx.template Alloc(out_accum); dev_ctx.template Alloc(softmax_lse_accum); } dynload::fa3_fwd_params_set_is_fp32(params_handle, false); dynload::fa3_fwd_params_set_oaccum_ptr( params_handle, const_cast(out_accum->data())); dynload::fa3_fwd_params_set_softmax_lseaccum_ptr( params_handle, const_cast(softmax_lse_accum->data())); dynload::fa3_fwd_params_set_oaccum_split_stride(params_handle, out_accum->strides()[0]); dynload::fa3_fwd_params_set_oaccum_row_stride( params_handle, out_accum->strides()[out_accum->strides().size() - 2]); dynload::fa3_fwd_params_set_oaccum_head_stride( params_handle, out_accum->strides()[out_accum->strides().size() - 3]); dynload::fa3_fwd_params_set_lseaccum_split_stride( params_handle, softmax_lse_accum->strides()[0]); dynload::fa3_fwd_params_set_lseaccum_head_stride( params_handle, softmax_lse_accum->strides()[softmax_lse_accum->strides().size() - 2]); } if (q_type == DataType::FLOAT8_E4M3FN) { if (q_descale_.is_initialized()) { DenseTensor q_descale = q_descale_.get(); CHECK_DEVICE(q_descale); CHECK_SHAPE(q_descale, batch_size, num_heads_k); dynload::fa3_fwd_params_set_q_descale_ptr( params_handle, const_cast(q_descale.data())); dynload::fa3_fwd_params_set_q_descale_batch_stride( params_handle, q_descale.strides()[0]); dynload::fa3_fwd_params_set_q_descale_head_stride(params_handle, q_descale.strides()[1]); } else { dynload::fa3_fwd_params_set_q_descale_ptr(params_handle, nullptr); } if (k_descale_.is_initialized()) { DenseTensor k_descale = k_descale_.get(); CHECK_DEVICE(k_descale); CHECK_SHAPE(k_descale, batch_size, num_heads_k); dynload::fa3_fwd_params_set_k_descale_ptr( params_handle, const_cast(k_descale.data())); dynload::fa3_fwd_params_set_k_descale_batch_stride( params_handle, k_descale.strides()[0]); dynload::fa3_fwd_params_set_k_descale_head_stride(params_handle, k_descale.strides()[1]); } else { dynload::fa3_fwd_params_set_k_descale_ptr(params_handle, nullptr); } if (v_descale_.is_initialized()) { DenseTensor v_descale = v_descale_.get(); CHECK_DEVICE(v_descale); CHECK_SHAPE(v_descale, batch_size, num_heads_k); dynload::fa3_fwd_params_set_v_descale_ptr( params_handle, const_cast(v_descale.data())); dynload::fa3_fwd_params_set_v_descale_batch_stride( params_handle, v_descale.strides()[0]); dynload::fa3_fwd_params_set_v_descale_head_stride(params_handle, v_descale.strides()[1]); } else { dynload::fa3_fwd_params_set_v_descale_ptr(params_handle, nullptr); } } #ifdef FLASHATTENTION_DISABLE_LOCAL PADDLE_ENFORCE_EQ( !dynload::fa3_fwd_params_get_is_local(params_handle), true, common::errors::InvalidArgument( "This flash attention build does not support local attention.")); #endif #ifdef FLASHATTENTION_DISABLE_SOFTCAP PADDLE_ENFORCE_EQ( dynload::fa3_fwd_params_get_softcap(params_handle), 0.0, common::errors::InvalidArgument( "This flash attention build does not support tanh softcapping.")); #endif #ifdef FLASHATTENTION_DISABLE_SPLIT PADDLE_ENFORCE_EQ(dynload::fa3_fwd_params_get_num_splits(params_handle), 1, common::errors::InvalidArgument( "This flash attention build does not support splits.")); #endif #ifdef FLASHATTENTION_DISABLE_PACKGQA PADDLE_ENFORCE_EQ( (!dynload::fa3_fwd_params_get_pack_gqa(params_handle) || dynload::fa3_fwd_params_get_arch(params_handle) < 90 || (dynload::fa3_fwd_params_get_page_table(params_handle) && !dynload::fa3_fwd_params_get_pagedkv_tma(params_handle)) || dynload::fa3_fwd_params_get_num_splits(params_handle) > 1), true, common::errors::InvalidArgument( "This flash attention build does not support pack_gqa.")); #endif #ifdef FLASHATTENTION_DISABLE_PAGEDKV PADDLE_ENFORCE_EQ( (!(dynload::fa3_fwd_params_get_page_table(params_handle) && !dynload::fa3_fwd_params_get_pagedkv_tma(params_handle))), true, common::errors::InvalidArgument( "This flash attention build does not support paged KV.")); #endif #ifdef FLASHATTENTION_DISABLE_APPENDKV PADDLE_ENFORCE_EQ( !k_new_.is_initialized(), true, common::errors::InvalidArgument( "This flash attention build does not support appending KV.")); #endif if (total_q > 0 && (total_k + dynload::fa3_fwd_params_get_total_knew(params_handle)) > 0 && num_heads_k > 0) { dynload::fa3_run_mha_fwd(params_handle, dev_ctx.stream()); if (dynload::fa3_fwd_params_get_num_splits(params_handle) > 1) { if (out_type == DataType::BFLOAT16) { // Since we want output in BF16. Otherwise fwd_combine will output to // FP16 dynload::fa3_fwd_params_set_is_bf16(params_handle, true); } // Unless there's seqused_q, for the purpose of attn_combine, we can just // treat it as batch=1 and seqlen = total_q, and don't need to dispatch to // Varlen there. However, with dynamic split, each row needs to know which // batch it belongs to to read the number of splits, so we just use the // varlen version of combine kernel. if (is_varlen_q && // !seqused_q_.has_value()) { if (is_varlen_q) { // params.b = 1; // params.seqlen_q = total_q; // } // } dynload::fa3_run_mha_fwd_combine( params_handle, dev_ctx.stream(), true /*enable_pdl*/); } } else if (total_q > 0 && num_heads_k > 0) { PADDLE_ENFORCE_EQ( (out->dtype() == DataType::BFLOAT16 || out->dtype() == DataType::FLOAT16 || out->dtype() == DataType::FLOAT8_E4M3FN), true, common::errors::InvalidArgument("flash attention 3 supports bfloat16, " "float16 and float8_e4m3fn only.")); // If seqlen_k == 0, then we have an empty tensor. We need to set the output // to 0. if (out->dtype() == DataType::BFLOAT16) { funcs::SetConstant set_zero; set_zero(dev_ctx, out, phi::bfloat16{0}); // If varlen we'll manually do the zero-ing } else if (out->dtype() == DataType::FLOAT16) { funcs::SetConstant set_zero; set_zero(dev_ctx, out, phi::float16{0}); // If varlen we'll manually do the zero-ing } else if (out->dtype() == DataType::FLOAT8_E4M3FN) { funcs::SetConstant set_zero; set_zero( dev_ctx, out, phi::float8_e4m3fn{0}); // If varlen we'll manually do the zero-ing } funcs::SetConstant set_infinity; set_infinity(dev_ctx, softmax_lse, std::numeric_limits::infinity()); } #else RaiseNotSupportedError(); #endif } template void FlashAttnV3Kernel(const Context &dev_ctx, const DenseTensor &q, const DenseTensor &k, const DenseTensor &v, const optional &q_v_, const optional &q_descale_, const optional &k_descale_, const optional &v_descale_, const float softmax_scale, bool is_causal, int window_size_left, int window_size_right, const float softcap, int num_splits, const bool manual_set_pack_gqa, const bool pack_gqa_, const int sm_margin, DenseTensor *out, DenseTensor *softmax_lse) { #ifdef PADDLE_WITH_FLASHATTN_V3 // umiswing: the following options have not been fully tested PADDLE_ENFORCE_EQ(q_v_.is_initialized(), false, common::errors::InvalidArgument("q_v_ is not supported")); PADDLE_ENFORCE_EQ( q_descale_.is_initialized(), false, common::errors::InvalidArgument("q_descale_ is not supported")); PADDLE_ENFORCE_EQ( k_descale_.is_initialized(), false, common::errors::InvalidArgument("k_descale_ is not supported")); PADDLE_ENFORCE_EQ( v_descale_.is_initialized(), false, common::errors::InvalidArgument("v_descale_ is not supported")); PADDLE_ENFORCE_EQ( window_size_left, -1, common::errors::InvalidArgument("window_size is not supported, please " "set window_size_left/right to -1")); PADDLE_ENFORCE_EQ( window_size_right, -1, common::errors::InvalidArgument("window_size is not supported, please " "set window_size_left/right to -1")); PADDLE_ENFORCE_EQ(softcap, 0, common::errors::InvalidArgument( "softcap is not supported, please set softcap to 0")); PADDLE_ENFORCE_EQ( num_splits, 1, common::errors::InvalidArgument( "num_splits is not supported, please set num_splits to 1")); PADDLE_ENFORCE_EQ(manual_set_pack_gqa, false, common::errors::InvalidArgument( "manual_set_pack_gqa is not supported, please set " "manual_set_pack_gqa to false")); PADDLE_ENFORCE_EQ( pack_gqa_, false, common::errors::InvalidArgument( "pack_gqa_ is not supported, please set pack_gqa_ to false")); PADDLE_ENFORCE_EQ( sm_margin, 0, common::errors::InvalidArgument( "sm_margin is not supported, please set sm_margin to 0")); DenseTensor out_accum; DenseTensor softmax_lse_accum; FlashAttnV3BaseKernel(dev_ctx, q, k, v, paddle::none, // k_new_ paddle::none, // v_new_ q_v_, paddle::none, // out_ paddle::none, // cu_seqlens_q_ paddle::none, // cu_seqlens_k_ paddle::none, // cu_seqlens_k_new_ paddle::none, // seqused_q_ paddle::none, // seqused_k_ paddle::none, // page_table_ paddle::none, // kv_batch_idx_ paddle::none, // leftpad_k_ paddle::none, // rotary_cos_ paddle::none, // rotary_sin_ q_descale_, k_descale_, v_descale_, paddle::none, // scheduler_metadata 0, // max_seqlen_q_ 0, // max_seqlen_k_ softmax_scale, is_causal, window_size_left, window_size_right, softcap, true, // is_rotary_interleaved num_splits, manual_set_pack_gqa, pack_gqa_, sm_margin, out, softmax_lse, &out_accum, &softmax_lse_accum); #else RaiseNotSupportedError(); #endif } template void FlashAttnV3VarlenKernel(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 &seqused_q, const optional &seqused_k, const optional &qv, const optional &q_descale, const optional &k_descale, const optional &v_descale, const Scalar &max_seqlen_q, const Scalar &max_seqlen_k, const float softmax_scale, const bool causal, const int window_size_left, const int window_size_right, const float softcap, const int num_splits, const bool manual_set_pack_gqa, const bool pack_gqa, const int sm_margin, DenseTensor *out, DenseTensor *softmax_lse) { #ifdef PADDLE_WITH_FLASHATTN_V3 // umiswing: the following options have not been fully tested PADDLE_ENFORCE_EQ(qv.is_initialized(), false, common::errors::InvalidArgument("q_v_ is not supported")); PADDLE_ENFORCE_EQ( q_descale.is_initialized(), false, common::errors::InvalidArgument("q_descale is not supported")); PADDLE_ENFORCE_EQ( k_descale.is_initialized(), false, common::errors::InvalidArgument("k_descale is not supported")); PADDLE_ENFORCE_EQ( v_descale.is_initialized(), false, common::errors::InvalidArgument("v_descale is not supported")); PADDLE_ENFORCE_EQ(softcap, 0, common::errors::InvalidArgument( "softcap is not supported, please set softcap to 0")); PADDLE_ENFORCE_EQ( num_splits, 1, common::errors::InvalidArgument( "num_splits is not supported, please set num_splits to 1")); PADDLE_ENFORCE_EQ(manual_set_pack_gqa, false, common::errors::InvalidArgument( "manual_set_pack_gqa is not supported, please set " "manual_set_pack_gqa to false")); PADDLE_ENFORCE_EQ( pack_gqa, false, common::errors::InvalidArgument( "pack_gqa is not supported, please set pack_gqa to false")); PADDLE_ENFORCE_EQ( sm_margin, 0, common::errors::InvalidArgument( "sm_margin is not supported, please set sm_margin to 0")); DenseTensor out_accum; DenseTensor softmax_lse_accum; const int64_t max_seqlen_q_ = max_seqlen_q.to(); const int64_t max_seqlen_k_ = max_seqlen_k.to(); FlashAttnV3BaseKernel(dev_ctx, q, k, v, paddle::none, // k_new_ paddle::none, // v_new_ qv, paddle::none, // out_ cu_seqlens_q, // cu_seqlens_q_ cu_seqlens_k, // cu_seqlens_k_ paddle::none, // cu_seqlens_k_new_ seqused_q, // seqused_q_ seqused_k, // seqused_k_ paddle::none, // page_table_ paddle::none, // kv_batch_idx_ paddle::none, // leftpad_k_ paddle::none, // rotary_cos_ paddle::none, // rotary_sin_ q_descale, k_descale, v_descale, paddle::none, // scheduler_metadata max_seqlen_q_, // max_seqlen_q_ max_seqlen_k_, // max_seqlen_k_ softmax_scale, causal, window_size_left, window_size_right, softcap, true, // is_rotary_interleaved num_splits, manual_set_pack_gqa, pack_gqa, sm_margin, out, softmax_lse, &out_accum, &softmax_lse_accum); #else RaiseNotSupportedError(); #endif } template void FlashMaskV2BaseKernel( const Context &dev_ctx, const DenseTensor &q, const DenseTensor &k, const DenseTensor &v, const optional &k_new_, // (b, s_k_new, h_k, d) or (total_k_new, h_k, d) if there is // cu_seqlens_k_new const optional &v_new_, // (b, s_k_new, h_k, dv) or (total_k_new, h_k, dv) if there is // cu_seqlens_k_new const optional &q_v_, // (b, s_q, h, dv) or (total_q_new, h, // dv) if there is cu_seqlens_q const optional &out_, // (b, s_q, h, dv) or (total_q, h, dv) if there is cu_seqlens_q const optional &cu_seqlens_q_, // b+1 const optional &cu_seqlens_k_, // b+1 const optional &cu_seqlens_k_new_, // b+1 const optional &seqused_q_, // b. If given, only this many elements of each batch // element's queries and outputs are used. const optional &seqused_k_, // b. If given, only this many elements of each batch // element's keys are used. const optional &page_table_, // (b_k, max_num_pages_per_seq) const optional &kv_batch_idx_, // b. indices to index into the KV cache const optional &leftpad_k_, // b const optional &rotary_cos_, // seqlen_ro x (rotary_dim / 2) const optional &rotary_sin_, // seqlen_ro x (rotary_dim / 2) const optional &q_descale_, // (b, h_k), not (b, h) const optional &k_descale_, // (b, h_k) const optional &v_descale_, // (b, h_k) const optional &scheduler_metadata_, // (b + 1) const optional &startend_row_indices_, // (b,h,s_1,[1,2,4]) const optional &block_mask_, // ((b,h,s// 128,s // 128) const optional &unique_id_, // used in distributed overlap NVSHMEM init with // unique_id (128B u8 CPU tensor) const int max_seqlen_q_, // if max_seqlen_q_ is set to 0, it indicates that it is // uninitialized and should not be referenced // TODO(tridao): check if we need max_seqlen_k const int max_seqlen_k_, // if max_seqlen_q_ is set to 0, it indicates that it is // uninitialized and should not be referenced const float softmax_scale, bool is_causal, int window_size_left, int window_size_right, const float softcap, const bool is_rotary_interleaved, // if true, rotary combines indices 0 & // 1, else indices 0 & rotary_dim / 2 int num_splits, const bool manual_set_pack_gqa, const bool pack_gqa_, // the pack_gqa_ will be used only if manual_set_pack_gqa is // set to True; otherwise, the internal heuristic // get_pack_gqa() from fa3 will decide whether to pack gqa const int sm_margin, const int rank, const int nranks, DenseTensor *out, DenseTensor *softmax_lse, DenseTensor *out_accum, DenseTensor *softmax_lse_accum) { #ifdef PADDLE_WITH_FLASHATTN_V3 // TODO(umiswing): support ampere int device_id = dev_ctx.GetPlace().GetDeviceId(); auto dprops = paddle::platform::GetDeviceProperties(device_id); const bool is_sm90 = dprops.major == 9 && dprops.minor == 0; PADDLE_ENFORCE_EQ(is_sm90, true, common::errors::Unavailable( "FlashAttention-3 only supports Hopper GPUs.")); auto q_type = q.dtype(); PADDLE_ENFORCE_EQ( (q_type == DataType::FLOAT16 || q_type == DataType::BFLOAT16 || q_type == DataType::FLOAT8_E4M3FN), true, common::errors::InvalidArgument( "FlashAttention-3 only supports fp16, bf16, and fp8_e4m3 data type")); PADDLE_ENFORCE_EQ(k.dtype(), q_type, common::errors::InvalidArgument( "query and key must have the same dtype")); PADDLE_ENFORCE_EQ(v.dtype(), q_type, common::errors::InvalidArgument( "query and value must have the same dtype")); CHECK_DEVICE(q); CHECK_DEVICE(k); CHECK_DEVICE(v); PADDLE_ENFORCE_EQ(q.strides()[q.strides().size() - 1], 1, common::errors::InvalidArgument( "Input tensor must have contiguous last dimension")); PADDLE_ENFORCE_EQ(k.strides()[k.strides().size() - 1], 1, common::errors::InvalidArgument( "Input tensor must have contiguous last dimension")); PADDLE_ENFORCE_EQ(v.strides()[v.strides().size() - 1], 1, common::errors::InvalidArgument( "Input tensor must have contiguous last dimension")); DenseTensor page_table; // const bool paged_KV = page_table_.has_value(); // umiswing: this is stupid but idk how to use optional const bool paged_KV = page_table_.is_initialized(); if (paged_KV) { page_table = page_table_.get(); CHECK_DEVICE(page_table); PADDLE_ENFORCE_EQ(page_table.dtype(), DataType::INT32, common::errors::InvalidArgument( "page_table must have dtype paddle.int32")); PADDLE_ENFORCE_EQ(page_table.strides()[page_table.strides().size() - 1], 1, common::errors::InvalidArgument( "page_table must have contiguous last dimension")); } // TODO(umiswing): support cusum DenseTensor cu_seqlens_q; // bool const is_varlen_q = cu_seqlens_q_.has_value(); // TODO(umiswing): this is stupid, must fix it (after understand // optional) const bool is_varlen_q = cu_seqlens_q_.is_initialized(); if (is_varlen_q) { cu_seqlens_q = cu_seqlens_q_.get(); CHECK_DEVICE(cu_seqlens_q); CHECK_CONTIGUOUS(cu_seqlens_q); PADDLE_ENFORCE_EQ(cu_seqlens_q.dtype(), DataType::INT32, common::errors::InvalidArgument( "cu_seqlens_q must have dtype paddle.int32")); PADDLE_ENFORCE_NE( max_seqlen_q_, 0, common::errors::InvalidArgument( "max_seqlen_q must be provided if cu_seqlens_q is provided")); } DenseTensor cu_seqlens_k; const bool is_varlen_k = cu_seqlens_k_.is_initialized(); if (is_varlen_k) { cu_seqlens_k = cu_seqlens_k_.get(); CHECK_DEVICE(cu_seqlens_k); CHECK_CONTIGUOUS(cu_seqlens_k); PADDLE_ENFORCE_EQ(cu_seqlens_k.dtype(), DataType::INT32, common::errors::InvalidArgument( "cu_seqlens_k must have dtype paddle.int32")); PADDLE_ENFORCE_NE( max_seqlen_k_, 0, common::errors::InvalidArgument( "max_seqlen_k must be provided if cu_seqlens_k is provided")); PADDLE_ENFORCE_EQ( !paged_KV, true, common::errors::InvalidArgument( "If cu_seqlens_k is passed in, then page table is not supported")); PADDLE_ENFORCE_EQ( !kv_batch_idx_, true, common::errors::InvalidArgument( "If cu_seqlens_k is passed in, then page table is not supported")); } auto const sizes = q.dims(); const int batch_size = !is_varlen_q ? sizes[0] : cu_seqlens_q.dims()[0] - 1; int seqlen_q = !is_varlen_q ? sizes[1] : max_seqlen_q_; int total_q = !is_varlen_q ? batch_size * sizes[1] : sizes[0]; int64_t num_heads = q.dims()[q.dims().size() - 2]; int64_t const head_size = q.dims()[q.dims().size() - 1]; int const head_size_v = v.dims()[v.dims().size() - 1]; int const max_num_pages_per_seq = !paged_KV ? 0 : page_table.dims()[1]; int const num_pages = !paged_KV ? 0 : k.dims()[0]; int const page_size = !paged_KV ? 1 : k.dims()[1]; int const seqlen_k = !is_varlen_k ? (!paged_KV ? k.dims()[1] : max_num_pages_per_seq * page_size) : max_seqlen_k_; int const total_k = !is_varlen_k ? batch_size * k.dims()[1] : k.dims()[0]; int const num_heads_k = k.dims()[k.dims().size() - 2]; int const batch_size_k = !paged_KV ? (!is_varlen_k ? k.dims()[0] : cu_seqlens_k.dims()[0] - 1) : page_table.dims()[0]; if (!kv_batch_idx_.is_initialized()) { PADDLE_ENFORCE_EQ(batch_size, batch_size_k, common::errors::InvalidArgument( "batch_size must be equal to batch_size_k")); } int const max_headdim = flashmaskv2_get_max_headdim(); PADDLE_ENFORCE_LE( head_size, max_headdim, common::errors::InvalidArgument( "FlashAttention forward only supports head dimension at most %d", max_headdim)); PADDLE_ENFORCE_EQ( num_heads % num_heads_k, 0, common::errors::InvalidArgument( "Number of heads in key/value must divide number of heads in query")); if (head_size_v != head_size) { PADDLE_ENFORCE_EQ( ((head_size > 128 && head_size <= 192 && head_size_v > 96 && head_size_v <= 128) || (head_size <= 64 && head_size_v <= 512)), true, common::errors::InvalidArgument( "If V headdim is different from Q/K dim, we only support " "Q/K headdim in (128, 192] and V headdim in (96, 128], " "or (Q/K <= 64 and V <= 512).")); PADDLE_ENFORCE_EQ(dprops.major, 9, common::errors::InvalidArgument( "Only Hopper supports different V headdim")); if (head_size_v > 256) { PADDLE_ENFORCE_EQ( (q_type == DataType::FLOAT16 || q_type == DataType::BFLOAT16), true, common::errors::InvalidArgument( "HeaddimV > 256 requires fp16 and bf16 data type")); } } bool const is_flashmask = startend_row_indices_.is_initialized(); bool const is_blockmask = block_mask_.is_initialized(); // This needs to go before kBlockM & kBlockN since we rely on the correct // window_size and is_causal to set kBlockM // TODO(tridao): check this if (window_size_left >= seqlen_k - 1) { window_size_left = -1; } if (window_size_right >= seqlen_q - 1) { window_size_right = -1; } // causal=true is the same as causal=false in this case if (seqlen_q == 1 && window_size_left == -1 && window_size_right == -1) { // Special case of hdim 128 where we want causal to have kBlockN=128, better // for pagedKV and TMA if (((head_size <= 64 || head_size > 128) || !paged_KV) && !is_flashmask) { is_causal = false; } } if (is_causal) { window_size_right = 0; } // There's a case where is_causal=false, window_size=(-1, 0). Then // set_params_fprop will set params.is_causal=true. If we don't have is_causal // here matching params.is_causal, we might get the wrong kBlockM. is_causal = window_size_left < 0 && window_size_right == 0; if (!is_varlen_q) { CHECK_SHAPE(q, batch_size, seqlen_q, num_heads, head_size); } else { CHECK_SHAPE(q, total_q, num_heads, head_size); CHECK_SHAPE(cu_seqlens_q, batch_size + 1); } if (!paged_KV) { if (!is_varlen_k) { CHECK_SHAPE(k, batch_size_k, seqlen_k, num_heads_k, head_size); CHECK_SHAPE(v, batch_size_k, seqlen_k, num_heads_k, head_size_v); } else { CHECK_SHAPE(k, total_k, num_heads_k, head_size); CHECK_SHAPE(v, total_k, num_heads_k, head_size_v); CHECK_SHAPE(cu_seqlens_k, batch_size + 1); } } else { CHECK_SHAPE(k, num_pages, page_size, num_heads_k, head_size); CHECK_SHAPE(v, num_pages, page_size, num_heads_k, head_size_v); CHECK_SHAPE(page_table, batch_size_k, max_num_pages_per_seq); } if (seqused_q_.is_initialized()) { auto seqused_q = seqused_q_.get(); PADDLE_ENFORCE_EQ( seqused_q.dtype(), DataType::INT32, common::errors::InvalidArgument("seqused_q must have dtype int32")); CHECK_DEVICE(seqused_q); CHECK_CONTIGUOUS(seqused_q); CHECK_SHAPE(seqused_q, batch_size); } if (seqused_k_.is_initialized()) { auto seqused_k = seqused_k_.get(); PADDLE_ENFORCE_EQ( seqused_k.dtype(), DataType::INT32, common::errors::InvalidArgument("seqused_k must have dtype int32")); CHECK_DEVICE(seqused_k); CHECK_CONTIGUOUS(seqused_k); CHECK_SHAPE(seqused_k, batch_size); } if (leftpad_k_.is_initialized()) { auto leftpad_k = leftpad_k_.get(); PADDLE_ENFORCE_EQ( leftpad_k.dtype(), DataType::INT32, common::errors::InvalidArgument("leftpad_k must have dtype int32")); CHECK_DEVICE(leftpad_k); CHECK_CONTIGUOUS(leftpad_k); CHECK_SHAPE(leftpad_k, batch_size); } // This is what we will template on bool const is_varlen = is_varlen_q || is_varlen_k || seqused_q_.is_initialized() || seqused_k_.is_initialized() || leftpad_k_.is_initialized(); #ifdef FLASHATTENTION_DISABLE_VARLEN PADDLE_ENFORCE_EQ(!is_varlen, true, common::errors::Unavailable( "This flash attention build does not support varlen.")); #endif int const alignment = q_type == DataType::FLOAT8_E4M3FN ? 16 : 8; PADDLE_ENFORCE_EQ(head_size % alignment, 0, common::errors::InvalidArgument( "head_size should be a multiple of %d", alignment)); PADDLE_ENFORCE_EQ(head_size_v % alignment, 0, common::errors::InvalidArgument( "head_size_v should be a multiple of %d", alignment)); auto out_type = q_type == DataType::FLOAT8_E4M3FN ? DataType::BFLOAT16 : q_type; if (out_.is_initialized()) { *out = out_.get(); PADDLE_ENFORCE_EQ( out->dtype(), out_type, common::errors::InvalidArgument( "For FP16/BF16 input, output must have the same dtype as " "inputs. For FP8 input, output must have dtype BF16")); CHECK_DEVICE((*out)); PADDLE_ENFORCE_EQ(out->strides()[out->strides().size() - 1], 1, common::errors::InvalidArgument( "Output tensor must have contiguous last dimension")); if (!is_varlen_q) { CHECK_SHAPE((*out), batch_size, seqlen_q, num_heads, head_size_v); } else { CHECK_SHAPE((*out), total_q, num_heads, head_size_v); } } else { if (!is_varlen_q) { out->Resize({batch_size, seqlen_q, num_heads, head_size_v}); } else { out->Resize({total_q, num_heads, head_size_v}); } if (q_type == DataType::FLOAT8_E4M3FN) { dev_ctx.template Alloc(out); } else { // umiswing: assuming T is Input Type dev_ctx.template Alloc(out); } } auto round_multiple = [](int x, int m) { return (x + m - 1) / m * m; }; int const head_size_rounded = flashmaskv2_round_up_headdim(head_size); int const head_size_v_rounded = flashmaskv2_round_up_headdim(head_size_v); int const seqlen_q_rounded = round_multiple(seqlen_q, 128); int const seqlen_k_rounded = round_multiple(seqlen_k, 128); if (!is_varlen_q) { softmax_lse->Resize({batch_size, num_heads, seqlen_q}); } else { softmax_lse->Resize({num_heads, total_q}); } dev_ctx.template Alloc(softmax_lse); FlashMask_fwd_params *params_handle = get_flashmask_fwd_params_handle(); dynload::flashmaskv2_clear_fwd_params_handle(params_handle); set_flashmaskv2_params_fprop( params_handle, batch_size, seqlen_q, seqlen_k, seqlen_q_rounded, seqlen_k_rounded, num_heads, num_heads_k, head_size, head_size_rounded, q, k, v, out, !is_varlen_q ? nullptr : cu_seqlens_q.data(), !is_varlen_k ? nullptr : cu_seqlens_k.data(), seqused_q_.is_initialized() ? const_cast(seqused_q_.get().data()) : nullptr, seqused_k_.is_initialized() ? const_cast(seqused_k_.get().data()) : nullptr, softmax_lse->data(), /*p_dropout=*/0.f, softmax_scale, window_size_left, window_size_right, dprops, softcap, sm_margin); dynload::flashmaskv2_fwd_params_set_total_q(params_handle, total_q); dynload::flashmaskv2_fwd_params_set_total_k(params_handle, total_k); dynload::flashmaskv2_fwd_params_set_b_k(params_handle, batch_size_k); dynload::flashmaskv2_fwd_params_set_dv(params_handle, head_size_v); dynload::flashmaskv2_fwd_params_set_dv_rounded(params_handle, head_size_v_rounded); if (leftpad_k_ .is_initialized()) { // This needs to be set before get_pagedkv_tma dynload::flashmaskv2_fwd_params_set_leftpad_k(params_handle, leftpad_k_.get().data()); } if (paged_KV) { dynload::flashmaskv2_fwd_params_set_page_table(params_handle, page_table.data()); dynload::flashmaskv2_fwd_params_set_page_table_batch_stride( params_handle, page_table.strides()[0]); } dynload::flashmaskv2_fwd_params_set_page_size(params_handle, page_size); dynload::flashmaskv2_fwd_params_set_num_pages(params_handle, num_pages); if (k_new_.is_initialized()) { // This needs to be set before get_pagedkv_tma DenseTensor k_new, v_new; PADDLE_ENFORCE_EQ( v_new_.is_initialized(), true, common::errors::InvalidArgument( "If k_new is supplied, v_new must also be passed in")); PADDLE_ENFORCE_EQ( seqused_k_.is_initialized(), true, common::errors::InvalidArgument( "If k_new is supplied, seqlens_k must also be passed in")); PADDLE_ENFORCE_LE( seqlen_q, seqlen_k, common::errors::InvalidArgument( "If k_new is supplied, it must have seqlen <= the seqlen " "of the KV cache")); DenseTensor cu_seqlens_k_new; bool const is_varlen_k_new = cu_seqlens_k_new_.is_initialized(); if (is_varlen_k_new) { cu_seqlens_k_new = cu_seqlens_k_new_.get(); CHECK_DEVICE(cu_seqlens_k_new); CHECK_CONTIGUOUS(cu_seqlens_k_new); PADDLE_ENFORCE_EQ(cu_seqlens_k_new.dtype(), DataType::INT32, common::errors::InvalidArgument( "cu_seqlens_k_new must have dtype paddle.int32")); } k_new = k_new_.get(); v_new = v_new_.get(); PADDLE_ENFORCE_EQ(k_new.dtype(), q_type, common::errors::InvalidArgument( "k_new must have the same dtype as query")); PADDLE_ENFORCE_EQ(v_new.dtype(), q_type, common::errors::InvalidArgument( "v_new must have the same dtype as query")); CHECK_DEVICE(k_new); CHECK_DEVICE(v_new); PADDLE_ENFORCE_EQ(k_new.strides()[k_new.strides().size() - 1], 1, common::errors::InvalidArgument( "k_new tensor must have contiguous last dimension")); PADDLE_ENFORCE_EQ(v_new.strides()[v_new.strides().size() - 1], 1, common::errors::InvalidArgument( "v_new tensor must have contiguous last dimension")); // We don't need max_seqlen_k_new, so seqlen_k_new can be whatever when // is_varlen_k_new int seqlen_k_new = !is_varlen_k_new ? k_new.dims()[1] : 0; int total_k_new = !is_varlen_k_new ? batch_size * k_new.dims()[1] : k_new.dims()[0]; if (!is_varlen_k_new) { CHECK_SHAPE(k_new, batch_size, seqlen_k_new, num_heads_k, head_size); CHECK_SHAPE(v_new, batch_size, seqlen_k_new, num_heads_k, head_size_v); } else { CHECK_SHAPE(k_new, total_k_new, num_heads_k, head_size); CHECK_SHAPE(v_new, total_k_new, num_heads_k, head_size_v); CHECK_SHAPE(cu_seqlens_k_new, batch_size + 1); } // umiswing: dump this to shared library dynload::flashmaskv2_fwd_params_set_seqlen_knew(params_handle, seqlen_k_new); dynload::flashmaskv2_fwd_params_set_total_knew(params_handle, total_k_new); dynload::flashmaskv2_fwd_params_set_knew_ptr(params_handle, (k_new.data())); dynload::flashmaskv2_fwd_params_set_vnew_ptr(params_handle, (v_new.data())); // All stride are in elements, not bytes. dynload::flashmaskv2_fwd_params_set_knew_row_stride( params_handle, k_new.strides()[k_new.strides().size() - 3]); dynload::flashmaskv2_fwd_params_set_vnew_row_stride( params_handle, v_new.strides()[v_new.strides().size() - 3]); dynload::flashmaskv2_fwd_params_set_knew_head_stride( params_handle, k_new.strides()[k_new.strides().size() - 2]); dynload::flashmaskv2_fwd_params_set_vnew_head_stride( params_handle, v_new.strides()[v_new.strides().size() - 2]); if (!is_varlen_k_new) { dynload::flashmaskv2_fwd_params_set_knew_batch_stride(params_handle, k_new.strides()[0]); dynload::flashmaskv2_fwd_params_set_vnew_batch_stride(params_handle, v_new.strides()[0]); } if (is_varlen_k_new) { dynload::flashmaskv2_fwd_params_set_cu_seqlens_knew( params_handle, cu_seqlens_k_new.data()); } } // 992 = 32 * 31 is the max supported batch in prepare_varlen_num_blocks // kernel bool const use_dynamic_split = is_varlen && dynload::flashmaskv2_fwd_params_get_b(params_handle) <= 992; // Temporarily set num_splits_dynamic_ptr to 1 since get_num_splits checks it dynload::flashmaskv2_fwd_params_set_num_splits_dynamic_ptr( params_handle, !use_dynamic_split ? nullptr : reinterpret_cast(1)); dynload::flashmaskv2_fwd_params_set_pagedkv_tma( params_handle, dynload::flashmaskv2_get_pagedkv_tma(params_handle)); if (num_splits <= 0) { num_splits = dynload::flashmaskv2_get_num_splits(params_handle); } dynload::flashmaskv2_fwd_params_set_num_splits(params_handle, num_splits); // Always enable PackGQA for Split, and get_pack_gqa requires // params.num_splits to decide const bool pack_gqa = manual_set_pack_gqa ? pack_gqa_ : dynload::flashmaskv2_get_pack_gqa(params_handle); dynload::flashmaskv2_fwd_params_set_pack_gqa(params_handle, pack_gqa); // This needs to be set after get_num_splits DenseTensor tile_count_semaphore; // Contains the semaphore and optionally // num_splits_dynamic // We don't use the persistent scheduler if Split and not Varlen const bool params_is_causal = dynload::flashmaskv2_fwd_params_get_is_causal(params_handle); const bool params_is_local = dynload::flashmaskv2_fwd_params_get_is_local(params_handle); const int params_num_splits = dynload::flashmaskv2_fwd_params_get_num_splits(params_handle); const int params_b = dynload::flashmaskv2_fwd_params_get_b(params_handle); const int params_arch = dynload::flashmaskv2_fwd_params_get_arch(params_handle); bool const scheduler_needs_semaphore = params_arch >= 90 ? true : ((params_is_causal && !is_varlen) || (is_varlen && params_num_splits > 1)); if (scheduler_needs_semaphore || use_dynamic_split) { int metadata_size = static_cast(scheduler_needs_semaphore) + static_cast(use_dynamic_split) * params_b; dynload::flashmaskv2_fwd_params_set_skip_scheduler_metadata_computation( params_handle, scheduler_metadata_.is_initialized()); if (scheduler_metadata_.is_initialized()) { DenseTensor scheduler_metadata = scheduler_metadata_.get(); CHECK_DEVICE(scheduler_metadata); CHECK_SHAPE(scheduler_metadata, metadata_size); CHECK_CONTIGUOUS(scheduler_metadata); PADDLE_ENFORCE_EQ(scheduler_metadata.dtype(), DataType::INT32, common::errors::InvalidArgument( "scheduler_metadata must have dtype int32")); tile_count_semaphore = scheduler_metadata; } else { tile_count_semaphore = Empty(dev_ctx, {metadata_size}); } if (scheduler_needs_semaphore && !use_dynamic_split) { funcs::SetConstant set_zero; set_zero(dev_ctx, &tile_count_semaphore, int32_t{0}); // If varlen we'll manually do the zero-ing } dynload::flashmaskv2_fwd_params_set_tile_count_semaphore( params_handle, scheduler_needs_semaphore ? (tile_count_semaphore.data()) : nullptr); dynload::flashmaskv2_fwd_params_set_num_splits_dynamic_ptr( params_handle, use_dynamic_split ? (tile_count_semaphore.data()) + 1 : nullptr); } if (q_v_.is_initialized()) { PADDLE_ENFORCE_LT(head_size, 64, common::errors::InvalidArgument( "q_v is only supported for head_size <= 64")); PADDLE_ENFORCE_EQ( (q_type == DataType::FLOAT16 || q_type == DataType::FLOAT16), true, common::errors::InvalidArgument( "q_v is only supported for fp16 and bf16 data type")); PADDLE_ENFORCE_EQ(params_arch, 90, common::errors::InvalidArgument( "q_v is only supported for Hopper GPUs")); DenseTensor q_v = q_v_.get(); PADDLE_ENFORCE_EQ(q_v.dtype(), q_type, common::errors::InvalidArgument( "q_v must have the same dtype as query")); CHECK_DEVICE(q_v); PADDLE_ENFORCE_EQ(q_v.strides()[q_v.strides().size() - 1], 1, common::errors::InvalidArgument( "q_v tensor must have contiguous last dimension")); if (!is_varlen_q) { CHECK_SHAPE(q_v, batch_size, seqlen_q, num_heads, head_size_v); } else { CHECK_SHAPE(q_v, total_q, num_heads, head_size_v); } dynload::flashmaskv2_fwd_params_set_qv_ptr(params_handle, (q_v.data())); // All stride are in elements, not bytes. dynload::flashmaskv2_fwd_params_set_qv_row_stride( params_handle, q_v.strides()[q_v.strides().size() - 3]); dynload::flashmaskv2_fwd_params_set_qv_head_stride( params_handle, q_v.strides()[q_v.strides().size() - 2]); if (!is_varlen_q) { dynload::flashmaskv2_fwd_params_set_qv_batch_stride(params_handle, q_v.strides()[0]); } } if (rotary_cos_.is_initialized()) { PADDLE_ENFORCE_EQ( k_new_.is_initialized(), true, common::errors::InvalidArgument( "If rotary cos/sin are provided, new key / value to be " "appended to KV cache must also be provided")); DenseTensor rotary_cos = rotary_cos_.get(); CHECK_DEVICE(rotary_cos); CHECK_CONTIGUOUS(rotary_cos); int params_rotary_dim = rotary_cos.dims()[1] * 2; dynload::flashmaskv2_fwd_params_set_rotary_dim(params_handle, params_rotary_dim); PADDLE_ENFORCE_LE( params_rotary_dim, head_size, common::errors::InvalidArgument("rotary_dim must be <= headdim")); PADDLE_ENFORCE_EQ( params_rotary_dim % 16, 0, common::errors::InvalidArgument( "Only rotary dimensions divisible by 16 are currently supported")); // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t seqlen_ro = rotary_cos.dims()[0]; if (paged_KV) { PADDLE_ENFORCE_GE( seqlen_ro, seqlen_k, common::errors::InvalidArgument( "cos/sin seqlen must be at least the seqlen of KV cache")); } CHECK_SHAPE(rotary_cos, seqlen_ro, params_rotary_dim / 2); PADDLE_ENFORCE_EQ(rotary_cos.dtype(), q_type, common::errors::InvalidArgument( "rotary_cos must have the same dtype as query")); PADDLE_ENFORCE_EQ( rotary_sin_.is_initialized(), true, common::errors::InvalidArgument( "If rotary cos is provided, rotary sin must also be provided")); auto rotary_sin = rotary_sin_.get(); CHECK_DEVICE(rotary_sin); CHECK_CONTIGUOUS(rotary_sin); CHECK_SHAPE(rotary_sin, seqlen_ro, params_rotary_dim / 2); PADDLE_ENFORCE_EQ(rotary_sin.dtype(), q_type, common::errors::InvalidArgument( "rotary_cos must have the same dtype as query")); dynload::flashmaskv2_fwd_params_set_rotary_cos_ptr(params_handle, (rotary_cos.data())); dynload::flashmaskv2_fwd_params_set_rotary_sin_ptr(params_handle, (rotary_sin.data())); dynload::flashmaskv2_fwd_params_set_is_rotary_interleaved( params_handle, is_rotary_interleaved); } else { dynload::flashmaskv2_fwd_params_set_rotary_dim(params_handle, 0); } if (kv_batch_idx_.is_initialized()) { DenseTensor kv_batch_idx = kv_batch_idx_.get(); CHECK_DEVICE(kv_batch_idx); CHECK_CONTIGUOUS(kv_batch_idx); PADDLE_ENFORCE_EQ( kv_batch_idx.dtype(), DataType::INT32, common::errors::InvalidArgument("kv_batch_idx must have dtype int32")); dynload::flashmaskv2_fwd_params_set_kv_batch_idx( params_handle, reinterpret_cast(kv_batch_idx.data())); } if (dynload::flashmaskv2_fwd_params_get_num_splits(params_handle) > 1) { PADDLE_ENFORCE_LE( dynload::flashmaskv2_fwd_params_get_num_splits(params_handle), 256, common::errors::InvalidArgument("num_splits > 256 not supported")); if (!is_varlen_q) { out_accum->Resize(make_ddim( {dynload::flashmaskv2_fwd_params_get_num_splits(params_handle), batch_size, num_heads, seqlen_q, head_size_v})); softmax_lse_accum->Resize(make_ddim( {dynload::flashmaskv2_fwd_params_get_num_splits(params_handle), batch_size, num_heads, seqlen_q})); dev_ctx.template Alloc(out_accum); dev_ctx.template Alloc(softmax_lse_accum); dynload::flashmaskv2_fwd_params_set_oaccum_batch_stride( params_handle, out_accum->strides()[1]); dynload::flashmaskv2_fwd_params_set_lseaccum_batch_stride( params_handle, softmax_lse_accum->strides()[1]); } else { out_accum->Resize(make_ddim( {dynload::flashmaskv2_fwd_params_get_num_splits(params_handle), num_heads, total_q, head_size_v})); softmax_lse_accum->Resize(make_ddim( {dynload::flashmaskv2_fwd_params_get_num_splits(params_handle), num_heads, total_q})); dev_ctx.template Alloc(out_accum); dev_ctx.template Alloc(softmax_lse_accum); } dynload::flashmaskv2_fwd_params_set_is_fp32(params_handle, false); dynload::flashmaskv2_fwd_params_set_oaccum_ptr(params_handle, (out_accum->data())); dynload::flashmaskv2_fwd_params_set_softmax_lseaccum_ptr( params_handle, (softmax_lse_accum->data())); dynload::flashmaskv2_fwd_params_set_oaccum_split_stride( params_handle, out_accum->strides()[0]); dynload::flashmaskv2_fwd_params_set_oaccum_row_stride( params_handle, out_accum->strides()[out_accum->strides().size() - 2]); dynload::flashmaskv2_fwd_params_set_oaccum_head_stride( params_handle, out_accum->strides()[out_accum->strides().size() - 3]); dynload::flashmaskv2_fwd_params_set_lseaccum_split_stride( params_handle, softmax_lse_accum->strides()[0]); dynload::flashmaskv2_fwd_params_set_lseaccum_head_stride( params_handle, softmax_lse_accum->strides()[softmax_lse_accum->strides().size() - 2]); } if (q_type == DataType::FLOAT8_E4M3FN) { if (q_descale_.is_initialized()) { DenseTensor q_descale = q_descale_.get(); CHECK_DEVICE(q_descale); CHECK_SHAPE(q_descale, batch_size, num_heads_k); dynload::flashmaskv2_fwd_params_set_q_descale_ptr( params_handle, (q_descale.data())); dynload::flashmaskv2_fwd_params_set_q_descale_batch_stride( params_handle, q_descale.strides()[0]); dynload::flashmaskv2_fwd_params_set_q_descale_head_stride( params_handle, q_descale.strides()[1]); } else { dynload::flashmaskv2_fwd_params_set_q_descale_ptr(params_handle, nullptr); } if (k_descale_.is_initialized()) { DenseTensor k_descale = k_descale_.get(); CHECK_DEVICE(k_descale); CHECK_SHAPE(k_descale, batch_size, num_heads_k); dynload::flashmaskv2_fwd_params_set_k_descale_ptr( params_handle, (k_descale.data())); dynload::flashmaskv2_fwd_params_set_k_descale_batch_stride( params_handle, k_descale.strides()[0]); dynload::flashmaskv2_fwd_params_set_k_descale_head_stride( params_handle, k_descale.strides()[1]); } else { dynload::flashmaskv2_fwd_params_set_k_descale_ptr(params_handle, nullptr); } if (v_descale_.is_initialized()) { DenseTensor v_descale = v_descale_.get(); CHECK_DEVICE(v_descale); CHECK_SHAPE(v_descale, batch_size, num_heads_k); dynload::flashmaskv2_fwd_params_set_v_descale_ptr( params_handle, (v_descale.data())); dynload::flashmaskv2_fwd_params_set_v_descale_batch_stride( params_handle, v_descale.strides()[0]); dynload::flashmaskv2_fwd_params_set_v_descale_head_stride( params_handle, v_descale.strides()[1]); } else { dynload::flashmaskv2_fwd_params_set_v_descale_ptr(params_handle, nullptr); } } #ifdef FLASHATTENTION_DISABLE_LOCAL PADDLE_ENFORCE_EQ( !dynload::flashmaskv2_fwd_params_get_is_local(params_handle), true, common::errors::InvalidArgument( "This flash attention build does not support local attention.")); #endif #ifdef FLASHATTENTION_DISABLE_SOFTCAP PADDLE_ENFORCE_EQ( dynload::flashmaskv2_fwd_params_get_softcap(params_handle), 0.0, common::errors::InvalidArgument( "This flash attention build does not support tanh softcapping.")); #endif #ifdef FLASHATTENTION_DISABLE_SPLIT PADDLE_ENFORCE_EQ( dynload::flashmaskv2_fwd_params_get_num_splits(params_handle), 1, common::errors::InvalidArgument( "This flash attention build does not support splits.")); #endif #ifdef FLASHATTENTION_DISABLE_PACKGQA PADDLE_ENFORCE_EQ( (!dynload::flashmaskv2_fwd_params_get_pack_gqa(params_handle) || dynload::flashmaskv2_fwd_params_get_arch(params_handle) < 90 || (dynload::flashmaskv2_fwd_params_get_page_table(params_handle) && !dynload::flashmaskv2_fwd_params_get_pagedkv_tma(params_handle)) || dynload::flashmaskv2_fwd_params_get_num_splits(params_handle) > 1), true, common::errors::InvalidArgument( "This flash attention build does not support pack_gqa.")); #endif #ifdef FLASHATTENTION_DISABLE_PAGEDKV PADDLE_ENFORCE_EQ( (!(dynload::flashmaskv2_fwd_params_get_page_table(params_handle) && !dynload::flashmaskv2_fwd_params_get_pagedkv_tma(params_handle))), true, common::errors::InvalidArgument( "This flash attention build does not support paged KV.")); #endif #ifdef FLASHATTENTION_DISABLE_APPENDKV PADDLE_ENFORCE_EQ( !k_new_.is_initialized(), true, common::errors::InvalidArgument( "This flash attention build does not support appending KV.")); #endif // flashmask DenseTensor startend_row_indices; if (is_flashmask) startend_row_indices = startend_row_indices_.get(); DenseTensor block_mask; if (is_blockmask) block_mask = block_mask_.get(); DenseTensor flashmask_maxmin, lt_start_row_indices, lt_end_row_indices, ut_start_row_indices, ut_end_row_indices; if (is_flashmask) { 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]")); auto flashmask_maxmin_shape = startend_row_indices.dims(); // TODO(umiswing): refine this block constraint (kBlockN % 32), since some // of kBlockN is not divisible by 32 flashmask_maxmin_shape[2] = // (flashmask_maxmin_shape[2] + 31) / 32 * 8; int device_id = dev_ctx.GetPlace().GetDeviceId(); auto dprops = paddle::platform::GetDeviceProperties(device_id); const bool is_sm90 = dprops.major == 9 && dprops.minor == 0; if (is_sm90) { // seqlen_k to nblock_seqlen, here we use kBlockN = 64 // as a conservative estimation (reduce allocation size) flashmask_maxmin_shape[2] = ((flashmask_maxmin_shape[2] + 63) / 64 + 3) / 4 * 4; // make sure this is the same with FlashMaskV3 fwd main loop static constexpr int flashmask_buffer_length = 16 * 1024; // estimate the upper bound of the possible chunk size static constexpr int chunk_padded_length = ((flashmask_buffer_length + 63) / 64 + 31) & 0xffffffe0; static constexpr int chunk_valid_length = ((flashmask_buffer_length + 63) / 64 + 3) & 0xfffffffc; const int num_chunk = (flashmask_maxmin_shape[2] + chunk_valid_length - 1) / chunk_valid_length; flashmask_maxmin_shape[2] = num_chunk * chunk_padded_length; } else { // seqlen_k to nblock_seqlen flashmask_maxmin_shape[2] = ((flashmask_maxmin_shape[2] + 31) / 32 + 3) / 4 * 4; } flashmask_maxmin_shape[3] = 8; flashmask_maxmin.set_type(DataType::INT32); flashmask_maxmin.Resize(flashmask_maxmin_shape); dev_ctx.template Alloc(&flashmask_maxmin); lt_start_row_indices = phi::Slice(dev_ctx, startend_row_indices, {3}, {0}, {1}); if (startend_row_indices.dims()[3] == 2) { if (!is_causal) { ut_end_row_indices = phi::Slice(dev_ctx, startend_row_indices, {3}, {1}, {2}); } else { lt_end_row_indices = phi::Slice(dev_ctx, startend_row_indices, {3}, {1}, {2}); } } else if (startend_row_indices.dims()[3] == 4) { ut_end_row_indices = phi::Slice(dev_ctx, startend_row_indices, {3}, {3}, {4}); lt_end_row_indices = phi::Slice(dev_ctx, startend_row_indices, {3}, {1}, {2}); ut_start_row_indices = phi::Slice(dev_ctx, startend_row_indices, {3}, {2}, {3}); } } if (is_blockmask) { PADDLE_ENFORCE_EQ( is_flashmask, true, common::errors::InvalidArgument( "blockmask should be used with flashmask at the same time ")); PADDLE_ENFORCE_EQ(block_mask.dims().size(), 4, common::errors::InvalidArgument( "blockmask receive blockmask_indices with dim " "[batch_size, num_heads, blocklen_q, blocklen_k]")); PADDLE_ENFORCE_EQ(block_mask.dims()[2], (seqlen_q + 127) / 128, common::errors::InvalidArgument( "blockmask is now only support blockdim_q = 128 ")); PADDLE_ENFORCE_EQ(block_mask.dims()[3], (seqlen_k + 127) / 128, common::errors::InvalidArgument( "blockmask is now only support blockdim_k = 128 ")); PADDLE_ENFORCE_EQ( block_mask.dims()[1], startend_row_indices.dims()[1], common::errors::InvalidArgument("blockmask is now only support same " "dim num_heads with flashmask ")); } if (is_blockmask) { // xhy: blockmask is now only support blockdim_q k = 128 dynload::flashmaskv2_fwd_params_set_m_block_dim(params_handle, 128); dynload::flashmaskv2_fwd_params_set_n_block_dim(params_handle, 128); dynload::flashmaskv2_fwd_params_set_block_mask_ptr( params_handle, (block_mask.data())); } if (is_flashmask) { if (lt_start_row_indices.initialized()) dynload::flashmaskv2_fwd_params_set_lt_start_ptr( params_handle, (lt_start_row_indices.data())); else dynload::flashmaskv2_fwd_params_set_lt_start_ptr(params_handle, nullptr); if (lt_end_row_indices.initialized()) dynload::flashmaskv2_fwd_params_set_lt_end_ptr( params_handle, (lt_end_row_indices.data())); else dynload::flashmaskv2_fwd_params_set_lt_end_ptr(params_handle, nullptr); if (ut_start_row_indices.initialized()) dynload::flashmaskv2_fwd_params_set_ut_start_ptr( params_handle, (ut_start_row_indices.data())); else dynload::flashmaskv2_fwd_params_set_ut_start_ptr(params_handle, nullptr); if (ut_end_row_indices.initialized()) dynload::flashmaskv2_fwd_params_set_ut_end_ptr( params_handle, (ut_end_row_indices.data())); else dynload::flashmaskv2_fwd_params_set_ut_end_ptr(params_handle, nullptr); if (flashmask_maxmin.initialized()) dynload::flashmaskv2_fwd_params_set_flashmask_maxmin_ptr( params_handle, (flashmask_maxmin.data())); else dynload::flashmaskv2_fwd_params_set_flashmask_maxmin_ptr(params_handle, nullptr); dynload::flashmaskv2_fwd_params_set_h_flashmask( params_handle, startend_row_indices.dims()[1]); dynload::flashmaskv2_fwd_params_set_h_h_flashmask_ratio( params_handle, num_heads / startend_row_indices.dims()[1]); // distributed settings #ifdef PADDLE_WITH_NVSHMEM PADDLE_ENFORCE_LE( nranks, 64, common::errors::InvalidArgument( "nranks for FlashMask overlap should <= 64, got: %d", nranks)); dynload::flashmaskv2_fwd_params_set_rank(params_handle, rank); dynload::flashmaskv2_fwd_params_set_nranks(params_handle, nranks); if (unique_id_.is_initialized()) { dynload::flashmaskv2_fwd_params_set_unique_id_ptr( params_handle, unique_id_.get().data()); VLOG(6) << "FlashMask overlap debug: unique_id_ptr set."; } else { dynload::flashmaskv2_fwd_params_set_unique_id_ptr(params_handle, nullptr); } VLOG(6) << "FlashMask overlap debug (rank and nranks): " << rank << ", " << nranks; #else VLOG(6) << "FlashMask overlap is not being used since PADDLE_WITH_NVSHMEM " "is not defined."; #endif // PADDLE_WITH_NVSHMEM } else { dynload::flashmaskv2_fwd_params_set_lt_start_ptr(params_handle, nullptr); dynload::flashmaskv2_fwd_params_set_lt_end_ptr(params_handle, nullptr); dynload::flashmaskv2_fwd_params_set_ut_start_ptr(params_handle, nullptr); dynload::flashmaskv2_fwd_params_set_ut_end_ptr(params_handle, nullptr); dynload::flashmaskv2_fwd_params_set_flashmask_maxmin_ptr(params_handle, nullptr); dynload::flashmaskv2_fwd_params_set_h_flashmask(params_handle, 0); dynload::flashmaskv2_fwd_params_set_h_h_flashmask_ratio(params_handle, 0); } if (total_q > 0 && (total_k + dynload::flashmaskv2_fwd_params_get_total_knew(params_handle)) > 0 && num_heads_k > 0) { dynload::flashmaskv2_run_mha_fwd(params_handle, dev_ctx.stream()); if (dynload::flashmaskv2_fwd_params_get_num_splits(params_handle) > 1) { if (out_type == DataType::BFLOAT16) { // Since we want output in BF16. Otherwise fwd_combine will output to // FP16 dynload::flashmaskv2_fwd_params_set_is_bf16(params_handle, true); } // Unless there's seqused_q, for the purpose of attn_combine, we can just // treat it as batch=1 and seqlen = total_q, and don't need to dispatch to // Varlen there. However, with dynamic split, each row needs to know which // batch it belongs to to read the number of splits, so we just use the // varlen version of combine kernel. if (is_varlen_q && // !seqused_q_.has_value()) { if (is_varlen_q) { // params.b = 1; // params.seqlen_q = total_q; // } // } dynload::flashmaskv2_run_mha_fwd_combine( params_handle, dev_ctx.stream(), true /*enable_pdl*/); } } else if (total_q > 0 && num_heads_k > 0) { PADDLE_ENFORCE_EQ( (out->dtype() == DataType::BFLOAT16 || out->dtype() == DataType::FLOAT16 || out->dtype() == DataType::FLOAT8_E4M3FN), true, common::errors::InvalidArgument("flash attention 3 supports bfloat16, " "float16 and float8_e4m3fn only.")); // If seqlen_k == 0, then we have an empty tensor. We need to set the output // to 0. if (out->dtype() == DataType::BFLOAT16) { funcs::SetConstant set_zero; set_zero(dev_ctx, out, phi::bfloat16{0}); // If varlen we'll manually do the zero-ing } else if (out->dtype() == DataType::FLOAT16) { funcs::SetConstant set_zero; set_zero(dev_ctx, out, phi::float16{0}); // If varlen we'll manually do the zero-ing } else if (out->dtype() == DataType::FLOAT8_E4M3FN) { funcs::SetConstant set_zero; set_zero( dev_ctx, out, phi::float8_e4m3fn{0}); // If varlen we'll manually do the zero-ing } funcs::SetConstant set_infinity; set_infinity(dev_ctx, softmax_lse, std::numeric_limits::infinity()); } #else RaiseNotSupportedError(); #endif } template void FlashMaskV2Kernel(const Context &dev_ctx, const DenseTensor &q, const DenseTensor &k, const DenseTensor &v, const DenseTensor &startend_row_indices, const optional &block_mask, const optional &unique_id, const float softmax_scale, bool is_causal, const int rank, const int nranks, DenseTensor *out, DenseTensor *softmax_lse) { #ifdef PADDLE_WITH_FLASHATTN_V3 // Handle 0-size tensors: return zeros without calling CUDA kernel // to avoid invalid memory access if (q.numel() == 0 || k.numel() == 0 || v.numel() == 0) { if (out) { funcs::SetConstant set_zero; set_zero(dev_ctx, out, T{0}); } if (softmax_lse) { funcs::SetConstant set_infinity; set_infinity( dev_ctx, softmax_lse, std::numeric_limits::infinity()); } return; } DenseTensor out_accum; DenseTensor softmax_lse_accum; FlashMaskV2BaseKernel(dev_ctx, q, k, v, paddle::none, // k_new_ paddle::none, // v_new_ paddle::none, // q_v_ paddle::none, // out_ paddle::none, // cu_seqlens_q_ paddle::none, // cu_seqlens_k_ paddle::none, // cu_seqlens_k_new_ paddle::none, // seqused_q_ paddle::none, // seqused_k_ paddle::none, // page_table_ paddle::none, // kv_batch_idx_ paddle::none, // leftpad_k_ paddle::none, // rotary_cos_ paddle::none, // rotary_sin_ paddle::none, // q_descale_ paddle::none, // k_descale_ paddle::none, // v_descale_ paddle::none, // scheduler_metadata_ startend_row_indices, block_mask, unique_id, 0, // max_seqlen_q_ 0, // max_seqlen_k_ softmax_scale, is_causal, -1, // window_size_left -1, // window_size_right float{0}, // softcap true, // is_rotary_interleaved 1, // num_splits false, // manual_set_pack_gqa false, // pack_gqa_ 0, // sm_margin rank, // dist CP settings nranks, // dist CP settings out, softmax_lse, &out_accum, &softmax_lse_accum); #else RaiseNotSupportedError(); #endif } template void FlashMaskV2GetUniqueIdInplace(const Context &dev_ctx, const DenseTensor &x, DenseTensor *out) { #if defined(PADDLE_WITH_CUDA) && defined(PADDLE_WITH_FLASHATTN_V3) bool valid_unique_id = dynload::flashmaskv2_get_nvshmem_unique_id(out->data()); if (!valid_unique_id) { // If FlashMask is not compiled with `WITH_DISTRIBUTED_OVERLAP` then this is // a zero tensor funcs::SetConstant set_zero; set_zero(dev_ctx, out, uint8_t{0}); } #else funcs::SetConstant set_zero; set_zero(dev_ctx, out, uint8_t{0}); #endif } } // namespace phi PD_REGISTER_KERNEL(flashmask_get_unique_id, CPU, ALL_LAYOUT, phi::FlashMaskV2GetUniqueIdInplace, uint8_t) { kernel->InputAt(0).SetBackend(phi::Backend::CPU); } PD_REGISTER_KERNEL(flash_attn_v3, GPU, ALL_LAYOUT, phi::FlashAttnV3Kernel, phi::float16, phi::bfloat16) {} PD_REGISTER_KERNEL(flash_attn_v3_varlen, GPU, ALL_LAYOUT, phi::FlashAttnV3VarlenKernel, phi::float16, phi::bfloat16) {} PD_REGISTER_KERNEL(flashmask_attention_v2, GPU, ALL_LAYOUT, phi::FlashMaskV2Kernel, phi::float16, phi::bfloat16) { kernel->InputAt(4).SetBackend(phi::Backend::ALL_BACKEND); // block_mask kernel->InputAt(5).SetBackend(phi::Backend::CPU); // nvshmem unique_id }