Files
paddlepaddle--paddle/paddle/phi/kernels/gpu/flash_attn_utils.h
T
2026-07-13 12:40:42 +08:00

419 lines
14 KiB
C++

// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/backends/gpu/gpu_launch_config.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/kernels/empty_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#ifdef PADDLE_WITH_FLASHATTN
#include "paddle/phi/backends/dynload/flashattn.h"
#endif
#ifdef PADDLE_WITH_FLASHATTN_V3
#include "paddle/phi/backends/dynload/flashattnv3.h"
#endif
namespace phi {
#ifdef PADDLE_WITH_FLASHATTN
static std::pair<uint64_t, uint64_t> GenerateRNGState(
const GPUContext& dev_ctx,
const optional<DenseTensor>& fixed_seed_offset,
const std::string& rng_name,
const int64_t batch_size,
const int64_t num_heads) {
if (fixed_seed_offset.get_ptr()) {
const int64_t* fixed_seed_offset_data =
fixed_seed_offset.get_ptr()->data<int64_t>();
uint64_t seed = static_cast<uint64_t>(fixed_seed_offset_data[0]);
uint64_t offset = static_cast<uint64_t>(fixed_seed_offset_data[1]);
return std::make_pair(seed, offset);
} else {
uint64_t inc = batch_size * num_heads * 32;
std::pair<uint64_t, uint64_t> seed_offset_pair;
if (rng_name != "") {
auto gen = phi::GetRandomSeedGenerator(rng_name);
seed_offset_pair = gen->IncrementOffset(inc);
} else {
auto* gen = dev_ctx.GetGenerator();
seed_offset_pair = gen->IncrementOffset(inc);
}
return seed_offset_pair;
}
}
static std::vector<int64_t> GetAttnMaskDims(const DenseTensor* attn_mask) {
std::vector<int64_t> mask_dim_4d;
if (attn_mask) {
const auto& origin_dims = attn_mask->dims();
auto rank = origin_dims.size();
PADDLE_ENFORCE_GE(
rank,
4,
common::errors::InvalidArgument(
"The number of dimensions of attn_mask is expected to be greater "
"or equal to 4, but received %d. The shape of attn_mask is {%s}",
rank,
origin_dims));
int64_t first_dim = 1;
for (int i = 0; i < rank - 3; i++) {
first_dim *= origin_dims[i];
}
mask_dim_4d = {first_dim,
origin_dims[rank - 3],
origin_dims[rank - 2],
origin_dims[rank - 1]};
}
return mask_dim_4d;
}
static std::vector<int64_t> GetAttnSparseMaskDims(
const DenseTensor* startend_row_indices, int64_t max_seqlen_q) {
std::vector<int64_t> mask_dim_4d;
if (startend_row_indices) {
const auto& dtype = startend_row_indices->dtype();
const auto& origin_dims = startend_row_indices->dims();
auto rank = origin_dims.size();
PADDLE_ENFORCE_EQ(
dtype,
DataType::INT32,
common::errors::InvalidArgument("dtype of startend_row_indices must be "
"int32, but received %d",
dtype));
PADDLE_ENFORCE_GE(
rank,
4,
common::errors::InvalidArgument(
"The number of dimensions of startend_row_indices is expected to "
"be greater or equal to 4, but received %d. The shape of "
"startend_row_indices is [%s]",
rank,
origin_dims));
int64_t first_dim = 1;
for (int i = 0; i < rank - 3; i++) {
first_dim *= origin_dims[i];
}
mask_dim_4d = {first_dim,
origin_dims[rank - 3],
origin_dims[rank - 2],
origin_dims[rank - 1]};
}
return mask_dim_4d;
}
struct FlashAttnParamsBase {
int version;
bool is_fwd;
int kBlockM;
int batch_size;
// for padded kernel, max_seqlen_q and seqlen_q is the same.
int64_t max_seqlen_q;
// for padded kernel, max_seqlen_k and seqlen_k is the same.
int64_t max_seqlen_k;
int num_heads;
int num_heads_k;
int head_size;
int seqlen_q_rounded;
int seqlen_k_rounded;
int head_size_rounded;
bool is_bf16;
bool is_fp8;
float softmax_scale;
std::vector<int64_t> softmax_lse_dims;
std::vector<int64_t> dpsum_dims;
bool causal;
std::vector<int64_t> mask_dims;
const DenseTensor* attn_mask_tensor;
const DenseTensor* startend_row_indices;
std::vector<int64_t> startend_row_indices_dims;
bool unpadded_lse;
int total_q;
FlashAttnParamsBase(const int _version,
const int _is_fwd,
const int _batch_size,
const int64_t _max_seqlen_q,
const int64_t _max_seqlen_k,
const int _num_heads,
const int _num_heads_k,
const int _head_size,
const float _scale,
const bool _causal,
const DataType q_dtype,
const optional<DenseTensor>& attn_mask,
const optional<DenseTensor>& startend_row_indices,
const bool _unpadded_lse,
const int _total_q)
: version(_version),
is_fwd(_is_fwd),
batch_size(_batch_size),
max_seqlen_q(_max_seqlen_q),
max_seqlen_k(_max_seqlen_k),
num_heads(_num_heads),
num_heads_k(_num_heads_k),
head_size(_head_size),
softmax_scale(_scale),
causal(_causal),
attn_mask_tensor(attn_mask.get_ptr()),
startend_row_indices(startend_row_indices.get_ptr()),
unpadded_lse(_unpadded_lse),
total_q(_total_q) {
is_bf16 = q_dtype == DataType::BFLOAT16;
// TODO(GuoxiaWang): check q, k, v dtype
auto round_multiple = [](int64_t x, int m) {
int64_t rounded = (x + m - 1) / m * static_cast<int64_t>(m);
PADDLE_ENFORCE_LE_INT_MAX(rounded, "flash_attn rounded sequence length");
return static_cast<int>(rounded);
};
// FLAGS_flash_attn_version
if (_version == 3 && !_is_fwd) {
kBlockM = head_size <= 64 ? 128 : (head_size < 256 ? 64 : 32);
head_size_rounded = head_size <= 64 ? 64 : round_multiple(head_size, 32);
} else {
kBlockM = 128;
head_size_rounded = round_multiple(head_size, 32);
}
seqlen_q_rounded = round_multiple(max_seqlen_q, kBlockM);
seqlen_k_rounded = round_multiple(max_seqlen_k, 128);
softmax_lse_dims = unpadded_lse ? std::vector<int64_t>{num_heads, total_q}
: std::vector<int64_t>{
batch_size, num_heads, max_seqlen_q};
dpsum_dims = std::vector<int64_t>{batch_size, num_heads, seqlen_q_rounded};
if (attn_mask_tensor) {
PADDLE_ENFORCE_EQ(
attn_mask->dtype(),
q_dtype,
common::errors::InvalidArgument(
"attn_mask is expected to have the same data type with q."));
mask_dims = GetAttnMaskDims(attn_mask_tensor);
}
startend_row_indices_dims = GetAttnSparseMaskDims(
startend_row_indices ? startend_row_indices.get_ptr() : nullptr,
max_seqlen_q);
if (startend_row_indices.is_initialized()) {
PADDLE_ENFORCE_EQ(
attn_mask_tensor,
nullptr,
common::errors::InvalidArgument(
"attn_mask and attn_mask_start_row_indices cannot be "
"set at same time."));
}
}
};
template <typename T>
struct FlashAttnFwdParamsV2 : public FlashAttnParamsBase {
float dropout;
bool return_softmax;
uint64_t seed;
uint64_t offset;
DenseTensor rng_state;
DenseTensor* softmax;
DenseTensor* softmax_lse;
DenseTensor* seed_offset;
DenseTensor tile_count_semaphore;
FlashAttnFwdParamsV2(const GPUContext& dev_ctx,
const int _version,
const int _batch_size,
const int64_t _max_seqlen_q,
const int64_t _max_seqlen_k,
const int _num_heads,
const int _num_heads_k,
const int _head_size,
const float _dropout,
const float _scale,
const bool _causal,
const bool _return_softmax,
const DataType q_dtype,
const bool is_test,
const std::string& rng_name,
const optional<DenseTensor>& fixed_seed_offset,
const optional<DenseTensor>& attn_mask,
const optional<DenseTensor>& startend_row_indices,
DenseTensor* _softmax,
DenseTensor* _softmax_lse,
DenseTensor* _seed_offset,
const bool _unpadded_lse,
const int _total_q)
: FlashAttnParamsBase(_version,
/*is_fwd=*/true,
_batch_size,
_max_seqlen_q,
_max_seqlen_k,
_num_heads,
_num_heads_k,
_head_size,
_scale,
_causal,
q_dtype,
attn_mask,
startend_row_indices,
_unpadded_lse,
_total_q),
dropout(_dropout),
return_softmax(_return_softmax),
softmax(_softmax),
softmax_lse(_softmax_lse),
seed_offset(_seed_offset) {
dropout = is_test ? 0.0f : _dropout;
// (umiswing): There is no suitable kernel for uint64_t, allocate in int64_t
// with the same size.
rng_state = Empty<int64_t>(dev_ctx, {2});
if (_dropout > 0.0f) {
auto seed_offset_pair = GenerateRNGState(
dev_ctx, fixed_seed_offset, rng_name, batch_size, num_heads);
seed = seed_offset_pair.first;
offset = seed_offset_pair.second;
} else {
seed = 0;
offset = 0;
}
seed_offset->Resize({2});
int64_t* seed_offset_data =
dev_ctx.template HostAlloc<int64_t>(seed_offset);
seed_offset_data[0] = static_cast<int64_t>(seed);
seed_offset_data[1] = static_cast<int64_t>(offset);
softmax_lse->Resize(softmax_lse_dims);
dev_ctx.template Alloc<float>(softmax_lse);
if (_version == 3) {
tile_count_semaphore = Full<int>(dev_ctx, {1}, static_cast<int>(0));
}
if (return_softmax) {
PADDLE_ENFORCE_EQ(
dropout > 0.0f,
true,
common::errors::InvalidArgument(
"return_softmax is only supported when dropout > 0.0"));
softmax->Resize(
{batch_size, num_heads, seqlen_q_rounded, seqlen_k_rounded});
dev_ctx.template Alloc<T>(softmax);
}
}
};
struct FlashAttnBwdParamsV2 : public FlashAttnParamsBase {
float dropout;
uint64_t seed;
uint64_t offset;
DenseTensor softmax_d;
DenseTensor dq_accum;
DenseTensor rng_state;
DenseTensor softmax_lse_log2;
DenseTensor dq_semaphore;
FlashAttnBwdParamsV2(const GPUContext& dev_ctx,
const int _version,
const int _batch_size,
const int64_t _max_seqlen_q,
const int64_t _max_seqlen_k,
const int _num_heads,
const int _num_heads_k,
const int _head_size,
const float _dropout,
const float _scale,
const bool _causal,
const DataType q_dtype,
const optional<DenseTensor>& attn_mask,
const optional<DenseTensor>& startend_row_indices,
const int64_t* seed_offset_data,
const bool _unpadded_lse,
const int _total_q)
: FlashAttnParamsBase(_version,
/*is_fwd=*/false,
_batch_size,
_max_seqlen_q,
_max_seqlen_k,
_num_heads,
_num_heads_k,
_head_size,
_scale,
_causal,
q_dtype,
attn_mask,
startend_row_indices,
_unpadded_lse,
_total_q),
dropout(_dropout) {
seed = static_cast<uint64_t>(seed_offset_data[0]);
offset = static_cast<uint64_t>(seed_offset_data[1]);
// (umiswing): There is no suitable kernel for uint64_t, allocate in int64_t
// with the same size.
rng_state = Empty<int64_t>(dev_ctx, {2});
// gradient of softmax_lse
softmax_d = Empty<float>(dev_ctx, dpsum_dims);
if (_version == 3) {
softmax_lse_log2 = Empty<float>(dev_ctx, softmax_lse_dims);
dq_semaphore = Empty<int>(
dev_ctx,
{(max_seqlen_q + kBlockM - 1) / kBlockM, batch_size, num_heads});
}
// an internal gradient of q, which will be further accumulated.
dq_accum = Empty<float>(
dev_ctx, {batch_size, num_heads, seqlen_q_rounded, head_size_rounded});
}
};
static void CheckFlashAttnStatus(const bool status) {
PADDLE_ENFORCE_EQ(status,
true,
common::errors::External(
"Error in Flash-Attention, detail information is: %s",
dynload::flash_attn_error()));
}
#endif
static void RaiseNotSupportedError(int version = 2) {
PADDLE_THROW(common::errors::Unimplemented(
"FlashAttention %d is unsupported, please check "
"the GPU compatibility and CUDA Version.",
version));
}
} // namespace phi