Files
2026-07-13 12:40:42 +08:00

1709 lines
65 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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/common/enforce.h"
#include "paddle/common/flags.h"
#include "paddle/phi/backends/gpu/gpu_context.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/kernels/empty_kernel.h"
#include "paddle/phi/kernels/funcs/math_function.h"
#include "paddle/phi/kernels/gpu/flash_attn_utils.h"
#include "paddle/phi/kernels/gpu/flash_attn_v3_utils.h"
#include "paddle/phi/kernels/concat_kernel.h"
#include "paddle/phi/kernels/gpu/flash_attn_v3_grad_kernel.h"
#include "paddle/phi/kernels/slice_kernel.h"
COMMON_DECLARE_bool(cudnn_deterministic);
namespace phi {
// b: batch_size
// s_q: seqlen_q
// s_k: seqlen_k
// h: num_heads
// h_k: num_heads_k
// d: head_size
template <typename T, typename Context>
void FlashAttnV3GradBaseKernel(
const Context &dev_ctx,
const DenseTensor
&dout, // (b, s_q, h, dv) or (total_q, h, dv) if there is cu_seqlens_q
const DenseTensor
&q, // (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
const DenseTensor
&k, // (b, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k
const DenseTensor
&v, // (b, s_k, h_k, dv) or (total_k, h_k, dv) if there is cu_seqlens_k
const DenseTensor
&out, // (b, s_q, h, dv) or (total_q, h, dv) if there is cu_seqlens_q
const DenseTensor
&softmax_lse, // (b, h, s_q) or (h, total_q) if there is cu_seqlens_q
const optional<DenseTensor>
&dq_, // (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
const optional<DenseTensor>
&dk_, // (b, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k
const optional<DenseTensor> &dv_, // (b, s_k, h_k, dv) or (total_k, h_k,
// dv) if there is cu_seqlens_k
const optional<DenseTensor> &cu_seqlens_q_, // b+1
const optional<DenseTensor> &cu_seqlens_k_, // b+1
const optional<DenseTensor>
&seqused_q_, // b. If given, only this many elements of each batch
// element's queries and outputs are used.
const optional<DenseTensor>
&seqused_k_, // b. If given, only this many elements of each batch
// element's keys are used.
int max_seqlen_q_,
int max_seqlen_k_,
float const softmax_scale,
bool is_causal,
int window_size_left,
int window_size_right,
float const softcap,
bool const deterministic,
int const sm_margin,
DenseTensor *dq,
DenseTensor *dk,
DenseTensor *dv,
DenseTensor *softmax_d,
DenseTensor *softmax_lse_log2,
DenseTensor *dq_accum,
DenseTensor *dk_accum,
DenseTensor *dv_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),
true,
common::errors::InvalidArgument(
"FlashAttention-3 bwd only support fp16 and bf16 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"));
PADDLE_ENFORCE_EQ(out.dtype(),
q_type,
common::errors::InvalidArgument(
"query and out must have the same dtype"));
PADDLE_ENFORCE_EQ(dout.dtype(),
q_type,
common::errors::InvalidArgument(
"query and dout must have the same dtype"));
CHECK_DEVICE(q);
CHECK_DEVICE(k);
CHECK_DEVICE(v);
CHECK_DEVICE(out);
CHECK_DEVICE(dout);
CHECK_DEVICE(softmax_lse);
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"));
PADDLE_ENFORCE_EQ(out.strides()[out.strides().size() - 1],
1,
common::errors::InvalidArgument(
"out tensor must have contiguous last dimension"));
PADDLE_ENFORCE_EQ(dout.strides()[dout.strides().size() - 1],
1,
common::errors::InvalidArgument(
"dout tensor must have contiguous last dimension"));
DenseTensor cu_seqlens_q;
bool const 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_GT(
max_seqlen_q_,
0,
common::errors::InvalidArgument(
"max_seqlen_q must be provided if cu_seqlens_q is provided"));
}
DenseTensor cu_seqlens_k;
bool const 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_GT(
max_seqlen_k_,
0,
common::errors::InvalidArgument(
"max_seqlen_k must be provided if cu_seqlens_k is provided"));
}
// 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();
#ifdef FLASHATTENTION_DISABLE_VARLEN
PADDLE_ENFORCE_EQ(!is_varlen,
true,
common::errors::Unavailable(
"This flash attention build does not support varlen."));
#endif
auto const sizes = q.dims();
int const batch_size = !is_varlen_q ? sizes[0] : cu_seqlens_q.dims()[0] - 1;
int const seqlen_q = !is_varlen_q ? sizes[1] : max_seqlen_q_;
int const total_q = !is_varlen_q ? batch_size * sizes[1] : sizes[0];
int const num_heads = q.dims()[q.dims().size() - 2];
int const head_size = q.dims()[q.dims().size() - 1];
int const head_size_v = v.dims()[v.dims().size() - 1];
int const seqlen_k = !is_varlen_k ? k.dims()[1] : 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];
PADDLE_ENFORCE_EQ(
head_size % 8,
0,
common::errors::InvalidArgument("head_size should be a multiple of 8"));
int const max_headdim = get_max_headdim();
PADDLE_ENFORCE_EQ(
head_size_v % 8,
0,
common::errors::InvalidArgument("head_size_v should be a multiple of 8"));
PADDLE_ENFORCE_LE(
std::max(head_size, head_size_v),
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"));
// This needs to go before kBlockM & kBlockN since we rely on the correct
// window_size and is_causal to set kBlockM
if (window_size_left >= seqlen_k - 1) {
window_size_left = -1;
}
if (window_size_right >= seqlen_q - 1) {
window_size_right = -1;
}
if (is_causal) {
window_size_right = 0;
}
// There's a case where is_causal=false, window_size=(-1, 0). Then
// set_params_bprop will set params.is_causal=true. If we don't have is_causal
// here matching params.is_causal, we might get the wrong kBlockM (and cause
// IMA).
is_causal = window_size_left < 0 && window_size_right == 0;
int const arch = dprops.major * 10 + dprops.minor;
int const head_size_rounded =
round_up_headdim(std::max(head_size, head_size_v));
int const head_size_v_rounded = head_size_rounded;
// Very important that these match the kernel configs
bool const is_local =
(window_size_left >= 0 || window_size_right >= 0) && !is_causal;
int const kBlockM_sm90 =
head_size_rounded <= 64
? (is_causal && softcap > 0.0 ? 96 : 128)
: (head_size_rounded <= 96
? 64
: (head_size_rounded <= 128
? (is_causal || is_local || softcap > 0.0 ? 64 : 80)
: 64));
int const kBlockM_sm80 = head_size_rounded <= 64 ? 128 : 64;
int const kBlockM_sm86 = head_size_rounded <= 192 ? 64 : 32;
int const kBlockM =
arch >= 90 ? kBlockM_sm90
: (arch == 86 || arch == 89 ? kBlockM_sm86 : kBlockM_sm80);
int const kBlockN_sm90 =
head_size_rounded <= 128 ? 128 : (head_size_rounded <= 192 ? 96 : 80);
int const kBlockN_sm80 =
head_size_rounded <= 128 ? 128 : (head_size_rounded <= 192 ? 80 : 64);
int const kBlockN_sm86 =
head_size_rounded <= 64
? 128
: (head_size_rounded <= 96
? 128
: (head_size_rounded <= 128
? 96
: (head_size_rounded <= 192 ? 64 : 64)));
int const kBlockN =
arch >= 90 ? kBlockN_sm90
: (arch == 86 || arch == 89 ? kBlockN_sm86 : kBlockN_sm80);
auto round_multiple = [](int x, int m) { return (x + m - 1) / m * m; };
int const seqlen_q_rounded = round_multiple(seqlen_q, kBlockM);
int const seqlen_k_rounded = round_multiple(seqlen_k, kBlockN);
int const total_q_padded_rounded =
round_multiple(total_q + batch_size * kBlockM, kBlockM);
int const total_k_padded_rounded =
round_multiple(total_k + batch_size * kBlockN, kBlockN);
if (!is_varlen_q) {
CHECK_SHAPE(q, batch_size, seqlen_q, num_heads, head_size);
CHECK_SHAPE(out, batch_size, seqlen_q, num_heads, head_size_v);
CHECK_SHAPE(dout, batch_size, seqlen_q, num_heads, head_size_v);
} else {
CHECK_SHAPE(q, total_q, num_heads, head_size);
CHECK_SHAPE(out, total_q, num_heads, head_size_v);
CHECK_SHAPE(dout, total_q, num_heads, head_size_v);
CHECK_SHAPE(cu_seqlens_q, batch_size + 1);
}
if (!is_varlen_k) {
CHECK_SHAPE(k, batch_size, seqlen_k, num_heads_k, head_size);
CHECK_SHAPE(v, batch_size, 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);
}
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 (dq_.is_initialized()) {
*dq = dq_.get();
PADDLE_ENFORCE_EQ(
dq->dtype(),
q_type,
common::errors::InvalidArgument("dq must have the same dtype as q"));
CHECK_DEVICE((*dq));
PADDLE_ENFORCE_EQ(dq->strides()[dq->strides().size() - 1],
1,
common::errors::InvalidArgument(
"dq must have contiguous last dimension"));
if (!is_varlen_q) {
CHECK_SHAPE((*dq), batch_size, seqlen_q, num_heads, head_size);
} else {
CHECK_SHAPE((*dq), total_q, num_heads, head_size);
}
} else {
*dq = EmptyLike<T, Context>(dev_ctx, q);
}
if (dk_.is_initialized()) {
*dk = dk_.get();
PADDLE_ENFORCE_EQ(
dk->dtype(),
q_type,
common::errors::InvalidArgument("dk must have the same dtype as q"));
CHECK_DEVICE((*dk));
PADDLE_ENFORCE_EQ(dk->strides()[dk->strides().size() - 1],
1,
common::errors::InvalidArgument(
"dk must have contiguous last dimension"));
if (!is_varlen_k) {
CHECK_SHAPE((*dk), batch_size, seqlen_k, num_heads_k, head_size);
} else {
CHECK_SHAPE((*dk), total_k, num_heads_k, head_size);
}
} else {
*dk = EmptyLike<T, Context>(dev_ctx, k);
}
if (dv_.is_initialized()) {
*dv = dv_.get();
PADDLE_ENFORCE_EQ(
dv->dtype(),
q_type,
common::errors::InvalidArgument("dv must have the same dtype as q"));
CHECK_DEVICE((*dv));
PADDLE_ENFORCE_EQ(dv->strides()[dv->strides().size() - 1],
1,
common::errors::InvalidArgument(
"dv must have contiguous last dimension"));
if (!is_varlen_k) {
CHECK_SHAPE((*dv), batch_size, seqlen_k, num_heads_k, head_size_v);
} else {
CHECK_SHAPE((*dv), total_k, num_heads_k, head_size_v);
}
} else {
*dv = EmptyLike<T, Context>(dev_ctx, v);
}
// Otherwise the kernel will be launched from cuda:0 device
// Cast to char to avoid compiler warning about narrowing
// Need softmax_d to have total_q_padded_rounded since we want its address to
// be aligned by 16/8 bytes for TMA / LDG.64
if (!is_varlen) {
if (softmax_d) {
// Need softmax_d to have seqlen_q_rounded since we want its address to be
// aligned by 16/8 bytes for TMA / LDG.64
softmax_d->Resize({batch_size, num_heads, seqlen_q_rounded});
}
if (softmax_lse_log2) {
softmax_lse_log2->Resize(
make_ddim({batch_size, num_heads, seqlen_q_rounded}));
}
} else {
if (softmax_d) {
softmax_d->Resize({num_heads, total_q_padded_rounded});
}
if (softmax_lse_log2) {
softmax_lse_log2->Resize({num_heads, total_q_padded_rounded});
}
}
if (softmax_d) {
dev_ctx.template Alloc<float>(softmax_d);
}
if (softmax_lse_log2) {
dev_ctx.template Alloc<float>(softmax_lse_log2);
}
if (dq_accum) {
if (!is_varlen) {
dq_accum->Resize(make_ddim(
{batch_size, num_heads, seqlen_q_rounded * head_size_rounded}));
} else {
dq_accum->Resize(
make_ddim({num_heads, total_q_padded_rounded * head_size_rounded}));
}
dev_ctx.template Alloc<float>(dq_accum);
}
if (num_heads_k != num_heads) { // MQA / GQA
if (!is_varlen) {
if (dk_accum) {
dk_accum->Resize(make_ddim(
{batch_size, num_heads_k, seqlen_k_rounded * head_size_rounded}));
}
if (dv_accum) {
dv_accum->Resize(make_ddim(
{batch_size, num_heads_k, seqlen_k_rounded * head_size_v_rounded}));
}
} else {
if (dk_accum) {
dk_accum->Resize(make_ddim(
{num_heads_k, total_k_padded_rounded, head_size_rounded}));
}
if (dv_accum) {
dv_accum->Resize(make_ddim(
{num_heads_k, total_k_padded_rounded, head_size_v_rounded}));
}
}
if (dk_accum) {
dev_ctx.template Alloc<float>(dk_accum);
}
if (dv_accum) {
dev_ctx.template Alloc<float>(dv_accum);
}
funcs::SetConstant<Context, float> set_zero;
if (dk_accum) {
set_zero(dev_ctx, dk_accum, float{0});
}
if (dv_accum) {
set_zero(dev_ctx, dv_accum, float{0});
}
}
Flash_bwd_params *params_handle = get_flash_bwd_params_handle();
dynload::fa3_clear_bwd_params_handle(params_handle);
set_params_dgrad(
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,
dout,
dq,
dk,
dv,
!is_varlen_q ? nullptr : cu_seqlens_q.data(),
!is_varlen_k ? nullptr : cu_seqlens_k.data(),
seqused_q_.is_initialized() ? const_cast<void *>(seqused_q_.get().data())
: nullptr,
seqused_k_.is_initialized() ? const_cast<void *>(seqused_k_.get().data())
: nullptr,
dq_accum ? dq_accum->data() : nullptr,
num_heads_k != num_heads && dk_accum ? dk_accum->data() : nullptr,
num_heads_k != num_heads && dv_accum ? dv_accum->data() : nullptr,
const_cast<void *>(softmax_lse.data()),
softmax_d ? const_cast<void *>(softmax_d->data()) : nullptr,
/*p_dropout=*/0.f,
softmax_scale,
window_size_left,
window_size_right,
dprops,
softcap,
deterministic,
sm_margin);
dynload::fa3_bwd_params_set_total_q(params_handle, total_q);
dynload::fa3_bwd_params_set_total_k(params_handle, total_k);
dynload::fa3_bwd_params_set_softmax_lse_log2_ptr(
params_handle, softmax_lse_log2 ? softmax_lse_log2->data() : nullptr);
dynload::fa3_bwd_params_set_dv(params_handle, head_size_v);
dynload::fa3_bwd_params_set_dv_rounded(params_handle, head_size_v_rounded);
// auto tile_count_semaphore = (params.is_causal || params.is_local) ?
// paddle::zeros({1}, opts.dtype(torch::kInt32)) : torch::empty({1},
// opts.dtype(torch::kInt32)); params.tile_count_semaphore =
// tile_count_semaphore.data_ptr<int>(); Will be zero'ed out in the backward
// preprocess kernel
DenseTensor dq_semaphore = Empty<int32_t>(
dev_ctx, {(seqlen_q + kBlockM - 1) / kBlockM, batch_size, num_heads});
dynload::fa3_bwd_params_set_dq_semaphore(params_handle,
dq_semaphore.data<int>());
DenseTensor dk_semaphore = Empty<int32_t>(
dev_ctx, {(seqlen_k + kBlockN - 1) / kBlockN, batch_size, num_heads_k});
DenseTensor dv_semaphore = Empty<int32_t>(
dev_ctx, {(seqlen_k + kBlockN - 1) / kBlockN, batch_size, num_heads_k});
if (num_heads_k != num_heads &&
dynload::fa3_bwd_params_get_deterministic(params_handle)) {
funcs::SetConstant<Context, int32_t> set_zero_dk;
set_zero_dk(dev_ctx, &dk_semaphore, static_cast<int32_t>(0));
funcs::SetConstant<Context, int32_t> set_zero_dv;
set_zero_dv(dev_ctx, &dv_semaphore, static_cast<int32_t>(0));
dynload::fa3_bwd_params_set_dk_semaphore(params_handle,
dk_semaphore.data<int>());
dynload::fa3_bwd_params_set_dv_semaphore(params_handle,
dv_semaphore.data<int>());
}
#ifdef FLASHATTENTION_DISABLE_LOCAL
PADDLE_ENABLE_EQ(
!dynload::fa3_bwd_params_get_is_local(params_handle),
true,
"This flash attention build does not support local attention.");
#endif
#ifdef FLASHATTENTION_DISABLE_SOFTCAP
PADDLE_ENABLE_EQ(
dynload::fa3_bwd_params_get_softcap(params_handle),
0.0,
"This flash attention build does not support tanh softcapping.");
#endif
if (total_q > 0 && total_k > 0 && num_heads_k > 0) {
dynload::fa3_run_mha_bwd(params_handle, dev_ctx.stream());
} else if (total_k > 0 && num_heads_k > 0) {
// If seqlen_q == 0, then we have an empty tensor. We need to set the output
// to 0.
funcs::SetConstant<Context, T> set_zero;
set_zero(dev_ctx, dk, T{0});
set_zero(dev_ctx, dv, T{0});
if (softmax_d) {
funcs::SetConstant<Context, float> set_zero_fp32;
set_zero_fp32(dev_ctx, softmax_d, float{0});
}
} else if (total_q > 0 && num_heads_k > 0) {
funcs::SetConstant<Context, T> set_zero;
set_zero(dev_ctx, dq, T{0});
if (softmax_d) {
funcs::SetConstant<Context, float> set_zero_fp32;
set_zero_fp32(dev_ctx, softmax_d, float{0});
}
}
#else
RaiseNotSupportedError();
#endif
}
template <typename T, typename Context>
void FlashAttnV3GradKernel(const Context &dev_ctx,
const DenseTensor &q,
const DenseTensor &k,
const DenseTensor &v,
const DenseTensor &out,
const DenseTensor &softmax_lse,
const DenseTensor &out_grad,
float const softmax_scale,
bool is_causal,
int window_size_left,
int window_size_right,
float const softcap,
int const sm_margin,
DenseTensor *dq,
DenseTensor *dk,
DenseTensor *dv) {
#ifdef PADDLE_WITH_FLASHATTN_V3
PADDLE_ENFORCE_EQ(softcap,
0,
common::errors::InvalidArgument(
"softcap is not supported, please set softcap to 0"));
PADDLE_ENFORCE_EQ(
sm_margin,
0,
common::errors::InvalidArgument(
"sm_margin is not supported, please set sm_margin to 0"));
// umiswing: fake grad tensor for FlashAttnV3GradBaseKernel
DenseTensor softmax_d;
DenseTensor softmax_lse_log2;
DenseTensor dq_accum;
DenseTensor dk_accum;
DenseTensor dv_accum;
const int64_t b = q.dims()[0];
const int64_t s_q = q.dims()[1];
const int64_t s_k = k.dims()[1];
const int64_t h_q = q.dims()[2];
const int64_t h_k = k.dims()[2];
const int64_t d_q = q.dims()[3];
const int64_t d_v = v.dims()[3];
const bool is_mla =
q.dims()[q.dims().size() - 1] > v.dims()[v.dims().size() - 1];
if (is_mla) {
PADDLE_ENFORCE_EQ(v.dims()[v.dims().size() - 1],
out.dims()[out.dims().size() - 1],
common::errors::InvalidArgument(
"head_dim_v and head_dim_o must be equal"));
PADDLE_ENFORCE_EQ(v.dims()[v.dims().size() - 2],
out.dims()[out.dims().size() - 2],
common::errors::InvalidArgument(
"num_heads_v and num_heads_o must be equal"));
PADDLE_ENFORCE_EQ(
v.dims()[v.dims().size() - 3],
out.dims()[out.dims().size() - 3],
common::errors::InvalidArgument("seqlen_v and seqlen_o must be equal"));
}
FlashAttnV3GradBaseKernel<T, Context>(dev_ctx,
out_grad,
q,
k,
v,
out,
softmax_lse,
paddle::none,
paddle::none,
paddle::none,
paddle::none,
paddle::none,
paddle::none,
paddle::none,
0,
0,
softmax_scale,
is_causal,
window_size_left,
window_size_right,
softcap,
FLAGS_cudnn_deterministic,
sm_margin,
dq,
dk,
dv,
&softmax_d,
&softmax_lse_log2,
&dq_accum,
&dk_accum,
&dv_accum);
// umiswing: some branch in upstream fa3 could have padded the head
// dimension
PADDLE_ENFORCE_EQ(
dq->dims()[dq->dims().size() - 1],
q.dims()[q.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dq != head dimension of q (%d != %d)",
dq->dims()[dq->dims().size() - 1],
q.dims()[q.dims().size() - 1]));
PADDLE_ENFORCE_EQ(
dk->dims()[dk->dims().size() - 1],
k.dims()[k.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dk != head dimension of k (%d != %d)",
dk->dims()[dk->dims().size() - 1],
k.dims()[k.dims().size() - 1]));
PADDLE_ENFORCE_EQ(
dv->dims()[dv->dims().size() - 1],
v.dims()[v.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dv != head dimension of v (%d != %d)",
dv->dims()[dv->dims().size() - 1],
v.dims()[v.dims().size() - 1]));
#else
RaiseNotSupportedError();
#endif
}
template <typename T, typename Context>
void FlashAttnV3VarlenGradKernel(const Context &dev_ctx,
const DenseTensor &q,
const DenseTensor &k,
const DenseTensor &v,
const DenseTensor &out,
const DenseTensor &softmax_lse,
const DenseTensor &cu_seqlens_q,
const DenseTensor &cu_seqlens_k,
const optional<DenseTensor> &seqused_q,
const optional<DenseTensor> &seqused_k,
const DenseTensor &out_grad,
float const softmax_scale,
const Scalar &max_seqlen_q,
const Scalar &max_seqlen_k,
bool const causal,
int const window_size_left,
int const window_size_right,
float const softcap,
int const sm_margin,
DenseTensor *dq,
DenseTensor *dk,
DenseTensor *dv) {
#ifdef PADDLE_WITH_FLASHATTN_V3
PADDLE_ENFORCE_EQ(softcap,
0,
common::errors::InvalidArgument(
"softcap is not supported, please set softcap to 0"));
PADDLE_ENFORCE_EQ(
sm_margin,
0,
common::errors::InvalidArgument(
"sm_margin is not supported, please set sm_margin to 0"));
const int64_t head_size = q.dims()[q.dims().size() - 1];
const int64_t head_size_v = v.dims()[v.dims().size() - 1];
PADDLE_ENFORCE_EQ(head_size,
head_size_v,
common::errors::InvalidArgument(
"This kernel does not support headdim != headdim_v, "
"but got headdim = %d and headdim_v = %d",
head_size,
head_size_v));
// umiswing: fake grad tensor for FlashAttnV3GradBaseKernel
DenseTensor softmax_d;
DenseTensor softmax_lse_log2;
DenseTensor dq_accum;
DenseTensor dk_accum;
DenseTensor dv_accum;
const int64_t max_seqlen_q_ = max_seqlen_q.to<int64_t>();
const int64_t max_seqlen_k_ = max_seqlen_k.to<int64_t>();
FlashAttnV3GradBaseKernel<T, Context>(dev_ctx,
out_grad,
q,
k,
v,
out,
softmax_lse,
paddle::none, // dq_
paddle::none, // dk_
paddle::none, // dv_
cu_seqlens_q,
cu_seqlens_k,
seqused_q,
seqused_k,
max_seqlen_q_,
max_seqlen_k_,
softmax_scale,
causal,
window_size_left,
window_size_right,
softcap,
FLAGS_cudnn_deterministic,
sm_margin,
dq,
dk,
dv,
&softmax_d,
&softmax_lse_log2,
&dq_accum,
&dk_accum,
&dv_accum);
// umiswing: some branch in upstream fa3 could have padded the head dimension
PADDLE_ENFORCE_EQ(
dq->dims()[dq->dims().size() - 1],
q.dims()[q.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dq != head dimension of q (%d != %d)",
dq->dims()[dq->dims().size() - 1],
q.dims()[q.dims().size() - 1]));
PADDLE_ENFORCE_EQ(
dk->dims()[dk->dims().size() - 1],
k.dims()[k.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dk != head dimension of k (%d != %d)",
dk->dims()[dk->dims().size() - 1],
k.dims()[k.dims().size() - 1]));
PADDLE_ENFORCE_EQ(
dv->dims()[dv->dims().size() - 1],
v.dims()[v.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dv != head dimension of v (%d != %d)",
dv->dims()[dv->dims().size() - 1],
v.dims()[v.dims().size() - 1]));
#else
RaiseNotSupportedError();
#endif
}
template <typename T, typename Context>
void FlashMaskV2GradBaseKernel(
const Context &dev_ctx,
const DenseTensor
&dout, // (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
const DenseTensor
&q, // (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
const DenseTensor
&k, // (b, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k
const DenseTensor
&v, // (b, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k
const DenseTensor
&out, // (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
const DenseTensor
&softmax_lse, // (b, h, s_q) or (h, total_q) if there is cu_seqlens_q
const optional<DenseTensor>
&dq_, // (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
const optional<DenseTensor>
&dk_, // (b, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k
const optional<DenseTensor>
&dv_, // (b, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k
const optional<DenseTensor> &cu_seqlens_q_, // b+1
const optional<DenseTensor> &cu_seqlens_k_, // b+1
const optional<DenseTensor>
&seqused_q_, // b. If given, only this many elements of each batch
// element's queries and outputs are used.
const optional<DenseTensor>
&seqused_k_, // b. If given, only this many elements of each batch
// element's keys are used.
const optional<DenseTensor> &startend_row_indices_,
const optional<DenseTensor> &block_mask_, // (b,h,s//128,s//128)
int max_seqlen_q_,
int max_seqlen_k_,
float const softmax_scale,
bool is_causal,
int window_size_left,
int window_size_right,
float const softcap,
bool const deterministic,
int const sm_margin,
int const rank,
int const nranks,
DenseTensor *dq,
DenseTensor *dk,
DenseTensor *dv,
DenseTensor *softmax_d,
DenseTensor *softmax_lse_log2,
DenseTensor *dq_accum,
DenseTensor *dk_accum,
DenseTensor *dv_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),
true,
common::errors::InvalidArgument(
"FlashAttention-3 bwd only support fp16 and bf16 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"));
PADDLE_ENFORCE_EQ(out.dtype(),
q_type,
common::errors::InvalidArgument(
"query and out must have the same dtype"));
PADDLE_ENFORCE_EQ(dout.dtype(),
q_type,
common::errors::InvalidArgument(
"query and dout must have the same dtype"));
CHECK_DEVICE(q);
CHECK_DEVICE(k);
CHECK_DEVICE(v);
CHECK_DEVICE(out);
CHECK_DEVICE(dout);
CHECK_DEVICE(softmax_lse);
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"));
PADDLE_ENFORCE_EQ(out.strides()[out.strides().size() - 1],
1,
common::errors::InvalidArgument(
"out tensor must have contiguous last dimension"));
PADDLE_ENFORCE_EQ(dout.strides()[dout.strides().size() - 1],
1,
common::errors::InvalidArgument(
"dout tensor must have contiguous last dimension"));
DenseTensor cu_seqlens_q;
bool const 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_GT(
max_seqlen_q_,
0,
common::errors::InvalidArgument(
"max_seqlen_q must be provided if cu_seqlens_q is provided"));
}
DenseTensor cu_seqlens_k;
bool const 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_GT(
max_seqlen_k_,
0,
common::errors::InvalidArgument(
"max_seqlen_k must be provided if cu_seqlens_k is provided"));
}
// 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();
#ifdef FLASHATTENTION_DISABLE_VARLEN
PADDLE_ENFORCE_EQ(!is_varlen,
true,
common::errors::Unavailable(
"This flash attention build does not support varlen."));
#endif
auto const sizes = q.dims();
int const batch_size = !is_varlen_q ? sizes[0] : cu_seqlens_q.dims()[0] - 1;
int const seqlen_q = !is_varlen_q ? sizes[1] : max_seqlen_q_;
int const total_q = !is_varlen_q ? batch_size * sizes[1] : sizes[0];
int const num_heads = q.dims()[q.dims().size() - 2];
int const head_size = q.dims()[q.dims().size() - 1];
int const seqlen_k = !is_varlen_k ? k.dims()[1] : 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];
PADDLE_ENFORCE_EQ(
head_size % 8,
0,
common::errors::InvalidArgument("head_size should be a multiple of 8"));
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"));
// This needs to go before kBlockM & kBlockN since we rely on the correct
// window_size and is_causal to set kBlockM
if (window_size_left >= seqlen_k - 1) {
window_size_left = -1;
}
if (window_size_right >= seqlen_q - 1) {
window_size_right = -1;
}
if (is_causal) {
window_size_right = 0;
}
// There's a case where is_causal=false, window_size=(-1, 0). Then
// set_params_bprop will set params.is_causal=true. If we don't have is_causal
// here matching params.is_causal, we might get the wrong kBlockM (and cause
// IMA).
is_causal = window_size_left < 0 && window_size_right == 0;
int const arch = dprops.major * 10 + dprops.minor;
int const head_size_rounded = flashmaskv2_round_up_headdim(head_size);
// Very important that these match the kernel configs
bool const is_local =
(window_size_left >= 0 || window_size_right >= 0) && !is_causal;
bool const is_flashmask = startend_row_indices_.is_initialized();
DenseTensor startend_row_indices;
if (is_flashmask) startend_row_indices = startend_row_indices_.get();
bool const has_softcap = softcap > 0.0;
// flashmask
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.dtype(),
DataType::INT32,
common::errors::InvalidArgument(
"flashmask_attention startend_row_indices must be INT32 type"));
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;
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<int32_t>(&flashmask_maxmin);
lt_start_row_indices =
Slice<int32_t>(dev_ctx, startend_row_indices, {3}, {0}, {1});
if (startend_row_indices.dims()[3] == 2) {
if (!is_causal) {
ut_end_row_indices =
Slice<int32_t>(dev_ctx, startend_row_indices, {3}, {1}, {2});
} else {
lt_end_row_indices =
Slice<int32_t>(dev_ctx, startend_row_indices, {3}, {1}, {2});
}
} else if (startend_row_indices.dims()[3] == 4) {
ut_end_row_indices =
Slice<int32_t>(dev_ctx, startend_row_indices, {3}, {3}, {4});
lt_end_row_indices =
Slice<int32_t>(dev_ctx, startend_row_indices, {3}, {1}, {2});
ut_start_row_indices =
Slice<int32_t>(dev_ctx, startend_row_indices, {3}, {2}, {3});
}
}
bool const is_blockmask = block_mask_.is_initialized();
DenseTensor block_mask;
if (is_blockmask) block_mask = block_mask_.get();
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 only supports blockdim_q = 128 now"));
PADDLE_ENFORCE_EQ(block_mask.dims()[3],
(seqlen_k + 127) / 128,
common::errors::InvalidArgument(
"blockmask only supports blockdim_k = 128 now"));
PADDLE_ENFORCE_EQ(
block_mask.dims()[1],
startend_row_indices.dims()[1],
common::errors::InvalidArgument(
"blockmask only supports same dim num_heads with flashmask now"));
PADDLE_ENFORCE_LE(seqlen_k,
1024 * 128,
common::errors::InvalidArgument(
"blockmask only supports seqlen <= 128k in bwd now"));
PADDLE_ENFORCE_LE(seqlen_q,
1024 * 128,
common::errors::InvalidArgument(
"blockmask only supports seqlen <= 128k in bwd now"));
}
const bool has_lt_start = lt_start_row_indices.initialized();
const bool has_lt_end = lt_end_row_indices.initialized();
const bool has_ut_start = ut_start_row_indices.initialized();
const bool has_ut_end = ut_end_row_indices.initialized();
// umiswing: The tile dispatch for flashmask is now different from fa3.
// Replacing the original ternary operator with lambda makes the code
// easier to reason about and less error-prone.
const auto [kBlockM_sm90, kBlockN_sm90] = [&]() -> std::pair<int, int> {
if (head_size_rounded <= 64) {
if (is_flashmask && !is_causal) {
return {64, 96};
} else if (is_causal && has_softcap || is_flashmask) {
return {96, 128};
} else {
return {128, 128};
}
} else if (head_size_rounded <= 128) {
// umiswing: by now, we reuse template instantiation of head dim 128 for
// head dim in range (64, 128], and therefore no separate dispatch for
// head dim in range (64, 96]
if (is_causal || is_local || has_softcap) {
return {64, 128};
} else {
if ((seqlen_q >= 1024 || seqlen_k >= 1024) &&
!(has_lt_end && has_ut_start)) {
return {64, 128};
} else {
return {64, 64};
}
}
} else if (head_size_rounded <= 256) {
// umiswing: by now, we reuse template instantiation of head dim 256 for
// head dim in range (128, 256], and therefore no separate dispatch for
// head dim in range (128, 192]
if (has_lt_end && has_ut_start) {
return {64, 32};
} else {
return {64, 64};
}
} else {
PADDLE_THROW(
common::errors::Unimplemented("head dim is rounded to %d, which is "
"not supported in FlashMask V3 now.",
head_size_rounded));
return {0, 0};
}
}();
int const kBlockM_sm80 = head_size_rounded <= 64 ? 128 : 64;
int const kBlockM_sm86 = head_size_rounded <= 192 ? 64 : 32;
int const kBlockM =
arch >= 90 ? kBlockM_sm90
: (arch == 86 || arch == 89 ? kBlockM_sm86 : kBlockM_sm80);
int const kBlockN_sm80 =
head_size_rounded <= 128 ? 128 : (head_size_rounded <= 192 ? 80 : 64);
int const kBlockN_sm86 =
head_size_rounded <= 64
? 128
: (head_size_rounded <= 96
? 128
: (head_size_rounded <= 128
? 96
: (head_size_rounded <= 192 ? 64 : 64)));
int const kBlockN =
arch >= 90 ? kBlockN_sm90
: (arch == 86 || arch == 89 ? kBlockN_sm86 : kBlockN_sm80);
auto round_multiple = [](int x, int m) { return (x + m - 1) / m * m; };
int const seqlen_q_rounded = round_multiple(seqlen_q, kBlockM);
// if KV head >= 4, we will consider using RS overlap
PADDLE_ENFORCE_LE(
nranks,
64,
common::errors::InvalidArgument(
"nranks for FlashMask overlap should <= 64, got: %d", nranks));
PADDLE_ENFORCE_EQ(
(nranks == 1) || (seqlen_k >= 4096 && seqlen_k <= 131072 &&
(seqlen_k & (seqlen_k - 1)) == 0),
true,
common::errors::InvalidArgument(
"If nranks > 1 (using overlap), currently only [4, 8, 16, 32, 64, "
"128]K seqlen_k is supported, got nranks = %d, seqlen_k = %d",
nranks,
seqlen_k));
const int chunks_per_seg = dynload::flashmaskv2_get_num_chunks_per_stage(
seqlen_k, nranks, num_heads_k);
PADDLE_ENFORCE_GT(
chunks_per_seg,
0,
common::errors::InvalidArgument(
"chunks_per_seg should be at least 1, but got: %d. Check whether "
"WITH_NVSHMEM is on for this Paddle compile.",
chunks_per_seg));
bool const use_rs_overlap = nranks > 1;
VLOG(6) << "FlashMask RS overlap: use rs: " << use_rs_overlap
<< ", num chunk: " << chunks_per_seg;
int const dkv_accum_s_scaler =
use_rs_overlap ? chunks_per_seg : nranks; // * cp_size
int const dkv_s_scaler =
use_rs_overlap ? 1 : nranks; // dk, dv remains local seqlen
int const seqlen_k_rounded_cp =
round_multiple(dkv_accum_s_scaler * seqlen_k, kBlockN);
int const total_q_padded_rounded =
round_multiple(total_q + batch_size * kBlockM, kBlockM);
int const total_k_padded_rounded =
round_multiple(total_k + batch_size * kBlockN, kBlockN);
if (!is_varlen_q) {
CHECK_SHAPE(q, batch_size, seqlen_q, num_heads, head_size);
CHECK_SHAPE(out, batch_size, seqlen_q, num_heads, head_size);
CHECK_SHAPE(dout, batch_size, seqlen_q, num_heads, head_size);
} else {
CHECK_SHAPE(q, total_q, num_heads, head_size);
CHECK_SHAPE(out, total_q, num_heads, head_size);
CHECK_SHAPE(dout, total_q, num_heads, head_size);
CHECK_SHAPE(cu_seqlens_q, batch_size + 1);
}
if (!is_varlen_k) {
CHECK_SHAPE(k, batch_size, seqlen_k, num_heads_k, head_size);
CHECK_SHAPE(v, batch_size, seqlen_k, num_heads_k, head_size);
} else {
CHECK_SHAPE(k, total_k, num_heads_k, head_size);
CHECK_SHAPE(v, total_k, num_heads_k, head_size);
CHECK_SHAPE(cu_seqlens_k, batch_size + 1);
}
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 (dq_.is_initialized()) {
*dq = dq_.get();
PADDLE_ENFORCE_EQ(
dq->dtype(),
q_type,
common::errors::InvalidArgument("dq must have the same dtype as q"));
CHECK_DEVICE((*dq));
PADDLE_ENFORCE_EQ(dq->strides()[dq->strides().size() - 1],
1,
common::errors::InvalidArgument(
"dq must have contiguous last dimension"));
if (!is_varlen_q) {
CHECK_SHAPE((*dq), batch_size, seqlen_q, num_heads, head_size);
} else {
CHECK_SHAPE((*dq), total_q, num_heads, head_size);
}
} else {
*dq = EmptyLike<T, Context>(dev_ctx, q);
}
PADDLE_ENFORCE_GT(nranks,
0,
common::errors::InvalidArgument(
"nranks should be at least 1, but got: %d", nranks));
if (nranks > 1) {
PADDLE_ENFORCE_EQ(
is_varlen_k,
false,
common::errors::InvalidArgument(
"when nranks > 1, FlashMask does not support varlen k."));
PADDLE_ENFORCE_LT(rank,
nranks,
common::errors::InvalidArgument(
"FlashMask distributed overlap requires "
"rank < nranks, but got rank = %d >= nranks %d.",
rank,
nranks));
}
auto GradTensorCheckSetter = [&](const DenseTensor &t,
const paddle::optional<DenseTensor> &dt_,
DenseTensor *dt,
const char *name) {
if (dt_.is_initialized()) {
*dt = dt_.get();
PADDLE_ENFORCE_EQ(dt->dtype(),
q_type,
common::errors::InvalidArgument(
"%s must have the same dtype as q", name));
CHECK_DEVICE((*dt));
PADDLE_ENFORCE_EQ(dt->strides()[dt->strides().size() - 1],
1,
common::errors::InvalidArgument(
"%s must have contiguous last dimension", name));
if (!is_varlen_k) {
CHECK_SHAPE(
(*dt), batch_size, seqlen_k * dkv_s_scaler, num_heads_k, head_size);
} else {
CHECK_SHAPE((*dt), total_k, num_heads_k, head_size);
}
} else {
// nranks > 1: using distributed overlap will actually compute with
// complete size. If nrank == 1, dkv_s_scaler will be 1
*dt = phi::Empty<T, Context>(
dev_ctx,
{batch_size, seqlen_k * dkv_s_scaler, num_heads_k, head_size});
}
};
GradTensorCheckSetter(k, dk_, dk, "dk");
GradTensorCheckSetter(v, dv_, dv, "dv");
// Otherwise the kernel will be launched from cuda:0 device
// Cast to char to avoid compiler warning about narrowing
// Need softmax_d to have total_q_padded_rounded since we want its address to
// be aligned by 16/8 bytes for TMA / LDG.64
if (!is_varlen) {
if (softmax_d) {
// Need softmax_d to have seqlen_q_rounded since we want its address to be
// aligned by 16/8 bytes for TMA / LDG.64
softmax_d->Resize({batch_size, num_heads, seqlen_q_rounded});
}
if (softmax_lse_log2) {
softmax_lse_log2->Resize(
make_ddim({batch_size, num_heads, seqlen_q_rounded}));
}
} else {
if (softmax_d) {
softmax_d->Resize({num_heads, total_q_padded_rounded});
}
if (softmax_lse_log2) {
softmax_lse_log2->Resize({num_heads, total_q_padded_rounded});
}
}
if (softmax_d) {
dev_ctx.template Alloc<float>(softmax_d);
}
if (softmax_lse_log2) {
dev_ctx.template Alloc<float>(softmax_lse_log2);
}
if (dq_accum) {
if (!is_varlen) {
dq_accum->Resize(make_ddim(
{batch_size, num_heads, seqlen_q_rounded * head_size_rounded}));
} else {
dq_accum->Resize(
make_ddim({num_heads, total_q_padded_rounded * head_size_rounded}));
}
dev_ctx.template Alloc<float>(dq_accum);
}
if (num_heads_k != num_heads) { // MQA / GQA
if (!is_varlen) {
// dk and dv accum should directly account for CP overlap
if (dk_accum) {
dk_accum->Resize(make_ddim({batch_size,
num_heads_k,
seqlen_k_rounded_cp * head_size_rounded}));
}
if (dv_accum) {
dv_accum->Resize(make_ddim({batch_size,
num_heads_k,
seqlen_k_rounded_cp * head_size_rounded}));
}
} else {
if (dk_accum) {
dk_accum->Resize(make_ddim(
{num_heads_k, total_k_padded_rounded, head_size_rounded}));
}
if (dv_accum) {
dv_accum->Resize(make_ddim(
{num_heads_k, total_k_padded_rounded, head_size_rounded}));
}
}
if (dk_accum) {
dev_ctx.template Alloc<float>(dk_accum);
}
if (dv_accum) {
dev_ctx.template Alloc<float>(dv_accum);
}
funcs::SetConstant<Context, float> set_zero;
if (dk_accum) {
set_zero(dev_ctx, dk_accum, float{0});
}
if (dv_accum) {
set_zero(dev_ctx, dv_accum, float{0});
}
}
FlashMask_bwd_params *params_handle = get_flashmask_bwd_params_handle();
dynload::flashmaskv2_clear_bwd_params_handle(params_handle);
set_flashmaskv2_params_dgrad(
params_handle,
batch_size,
seqlen_q,
seqlen_k,
seqlen_q_rounded,
seqlen_k_rounded_cp, // length of grad accum will be scaled for CP
// overlap
num_heads,
num_heads_k,
head_size,
head_size_rounded,
q,
k,
v,
out,
dout,
dq,
dk,
dv,
!is_varlen_q ? nullptr : cu_seqlens_q.data(),
!is_varlen_k ? nullptr : cu_seqlens_k.data(),
seqused_q_.is_initialized() ? const_cast<void *>(seqused_q_.get().data())
: nullptr,
seqused_k_.is_initialized() ? const_cast<void *>(seqused_k_.get().data())
: nullptr,
dq_accum ? dq_accum->data() : nullptr,
num_heads_k != num_heads && dk_accum ? dk_accum->data() : nullptr,
num_heads_k != num_heads && dv_accum ? dv_accum->data() : nullptr,
const_cast<void *>(softmax_lse.data()),
softmax_d ? (softmax_d->data()) : nullptr,
/*p_dropout=*/0.f,
softmax_scale,
window_size_left,
window_size_right,
dprops,
softcap,
deterministic,
sm_margin);
dynload::flashmaskv2_bwd_params_set_total_q(params_handle, total_q);
dynload::flashmaskv2_bwd_params_set_total_k(params_handle, total_k);
dynload::flashmaskv2_bwd_params_set_softmax_lse_log2_ptr(
params_handle, softmax_lse_log2 ? softmax_lse_log2->data() : nullptr);
dynload::flashmaskv2_bwd_params_set_dv(
params_handle,
head_size); // We don't support hdim_v being
// different from hdim_qk for now
DenseTensor tile_count_semaphore;
if (arch >= 90) {
tile_count_semaphore = Full<int32_t, Context>(dev_ctx, {1}, 0);
dynload::flashmaskv2_bwd_params_set_tile_count_semaphore(
params_handle, tile_count_semaphore.data<int>());
} else {
dynload::flashmaskv2_bwd_params_set_tile_count_semaphore(params_handle,
nullptr);
}
DenseTensor dq_semaphore = Empty<int32_t>(
dev_ctx, {(seqlen_q + kBlockM - 1) / kBlockM, batch_size, num_heads});
dynload::flashmaskv2_bwd_params_set_dq_semaphore(params_handle,
dq_semaphore.data<int>());
// dk_semaphore should have the same seqlen with dk_accum
DenseTensor dk_semaphore =
Empty<int32_t>(dev_ctx,
{(seqlen_k * dkv_accum_s_scaler + kBlockN - 1) / kBlockN,
batch_size,
num_heads_k});
DenseTensor dv_semaphore =
Empty<int32_t>(dev_ctx,
{(seqlen_k * dkv_accum_s_scaler + kBlockN - 1) / kBlockN,
batch_size,
num_heads_k});
if (num_heads_k != num_heads &&
dynload::flashmaskv2_bwd_params_get_deterministic(params_handle)) {
// xiangrui: we need to zero them out
funcs::SetConstant<Context, int32_t> set_zero_dk;
set_zero_dk(dev_ctx, &dk_semaphore, static_cast<int32_t>(0));
funcs::SetConstant<Context, int32_t> set_zero_dv;
set_zero_dv(dev_ctx, &dv_semaphore, static_cast<int32_t>(0));
dynload::flashmaskv2_bwd_params_set_dk_semaphore(params_handle,
dk_semaphore.data<int>());
dynload::flashmaskv2_bwd_params_set_dv_semaphore(params_handle,
dv_semaphore.data<int>());
}
if (is_flashmask) {
if (lt_start_row_indices.initialized())
dynload::flashmaskv2_bwd_params_set_lt_start_ptr(
params_handle, (lt_start_row_indices.data<int32_t>()));
else
dynload::flashmaskv2_bwd_params_set_lt_start_ptr(params_handle, nullptr);
if (lt_end_row_indices.initialized())
dynload::flashmaskv2_bwd_params_set_lt_end_ptr(
params_handle, (lt_end_row_indices.data<int32_t>()));
else
dynload::flashmaskv2_bwd_params_set_lt_end_ptr(params_handle, nullptr);
if (ut_start_row_indices.initialized())
dynload::flashmaskv2_bwd_params_set_ut_start_ptr(
params_handle, (ut_start_row_indices.data<int32_t>()));
else
dynload::flashmaskv2_bwd_params_set_ut_start_ptr(params_handle, nullptr);
if (ut_end_row_indices.initialized())
dynload::flashmaskv2_bwd_params_set_ut_end_ptr(
params_handle, (ut_end_row_indices.data<int32_t>()));
else
dynload::flashmaskv2_bwd_params_set_ut_end_ptr(params_handle, nullptr);
if (flashmask_maxmin.initialized())
dynload::flashmaskv2_bwd_params_set_flashmask_maxmin_ptr(
params_handle, (flashmask_maxmin.data<int32_t>()));
else
dynload::flashmaskv2_bwd_params_set_flashmask_maxmin_ptr(params_handle,
nullptr);
dynload::flashmaskv2_bwd_params_set_h_flashmask(
params_handle, startend_row_indices.dims()[1]);
dynload::flashmaskv2_bwd_params_set_h_h_flashmask_ratio(
params_handle, num_heads / startend_row_indices.dims()[1]);
#ifdef PADDLE_WITH_NVSHMEM
// only when NVSHMEM is compiled in paddle, can we use the following
dynload::flashmaskv2_bwd_params_set_rank(params_handle, rank);
dynload::flashmaskv2_bwd_params_set_nranks(params_handle, nranks);
#endif // PADDLE_WITH_NVSHMEM
} else {
dynload::flashmaskv2_bwd_params_set_lt_start_ptr(params_handle, nullptr);
dynload::flashmaskv2_bwd_params_set_lt_end_ptr(params_handle, nullptr);
dynload::flashmaskv2_bwd_params_set_ut_start_ptr(params_handle, nullptr);
dynload::flashmaskv2_bwd_params_set_ut_end_ptr(params_handle, nullptr);
dynload::flashmaskv2_bwd_params_set_flashmask_maxmin_ptr(params_handle,
nullptr);
dynload::flashmaskv2_bwd_params_set_h_flashmask(params_handle, 0);
dynload::flashmaskv2_bwd_params_set_h_h_flashmask_ratio(params_handle, 0);
}
if (is_blockmask) {
// xhy: blockmask is now only support blockdim_q k = 128
dynload::flashmaskv2_bwd_params_set_m_block_dim(params_handle, 128);
dynload::flashmaskv2_bwd_params_set_n_block_dim(params_handle, 128);
dynload::flashmaskv2_bwd_params_set_block_mask_ptr(
params_handle, (block_mask.data<int32_t>()));
}
#ifdef FLASHATTENTION_DISABLE_LOCAL
PADDLE_ENABLE_EQ(
!dynload::flashmaskv2_bwd_params_get_is_local(params_handle),
true,
"This flash attention build does not support local attention.");
#endif
#ifdef FLASHATTENTION_DISABLE_SOFTCAP
PADDLE_ENABLE_EQ(
dynload::flashmaskv2_bwd_params_get_softcap(params_handle),
0.0,
"This flash attention build does not support tanh softcapping.");
#endif
if (total_q > 0 && total_k > 0 && num_heads_k > 0) {
dynload::flashmaskv2_run_mha_bwd(params_handle, dev_ctx.stream());
} else if (total_k > 0 && num_heads_k > 0) {
// If seqlen_q == 0, then we have an empty tensor. We need to set the output
// to 0.
funcs::SetConstant<Context, T> set_zero;
set_zero(dev_ctx, dk, T{0});
set_zero(dev_ctx, dv, T{0});
if (softmax_d) {
funcs::SetConstant<Context, float> set_zero_fp32;
set_zero_fp32(dev_ctx, softmax_d, float{0});
}
} else if (total_q > 0 && num_heads_k > 0) {
funcs::SetConstant<Context, T> set_zero;
set_zero(dev_ctx, dq, T{0});
if (softmax_d) {
funcs::SetConstant<Context, float> set_zero_fp32;
set_zero_fp32(dev_ctx, softmax_d, float{0});
}
}
#else
RaiseNotSupportedError();
#endif
}
template <typename T, typename Context>
void FlashMaskV2GradKernel(
const Context &dev_ctx,
const DenseTensor &q,
const DenseTensor &k,
const DenseTensor &v,
const DenseTensor &out,
const DenseTensor &softmax_lse,
const DenseTensor &startend_row_indices, // TODO(xiehaoyang): remove this
const optional<DenseTensor> &block_mask,
const DenseTensor &out_grad,
float const softmax_scale,
bool is_causal,
int rank,
int nranks,
DenseTensor *dq,
DenseTensor *dk,
DenseTensor *dv) {
#ifdef PADDLE_WITH_FLASHATTN_V3
PADDLE_ENFORCE_EQ(
q.dims()[q.dims().size() - 1],
v.dims()[v.dims().size() - 1],
common::errors::InvalidArgument("head_dim_q != head_dim_v (%d != %d)",
q.dims()[q.dims().size() - 1],
v.dims()[v.dims().size() - 1]));
// umiswing: fake grad tensor for FlashAttnV3GradBaseKernel
DenseTensor softmax_d;
DenseTensor softmax_lse_log2;
DenseTensor dq_accum;
DenseTensor dk_accum;
DenseTensor dv_accum;
FlashMaskV2GradBaseKernel<T, Context>(
dev_ctx,
out_grad,
q,
k,
v,
out,
softmax_lse,
paddle::none, // dq_
paddle::none, // dk_
paddle::none, // dv_
paddle::none,
paddle::none,
paddle::none,
paddle::none,
startend_row_indices,
block_mask,
0, // max_seqlen_q,
0, // max_seqlen_k,
softmax_scale,
is_causal,
-1, // window_size_left,
-1, // window_size_right,
0, // softcap,
FLAGS_cudnn_deterministic, // deterministic,
0, // sm_margin,
rank,
nranks,
dq,
dk,
dv,
&softmax_d,
&softmax_lse_log2,
&dq_accum,
&dk_accum,
&dv_accum);
// umiswing: some branch in upstream fa3 could have padded the head dimension
PADDLE_ENFORCE_EQ(
dq->dims()[dq->dims().size() - 1],
out_grad.dims()[out_grad.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dq != head dimension of out_grad (%d != %d)",
dq->dims()[dq->dims().size() - 1],
out_grad.dims()[out_grad.dims().size() - 1]));
PADDLE_ENFORCE_EQ(
dk->dims()[dk->dims().size() - 1],
out_grad.dims()[out_grad.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dk != head dimension of out_grad (%d != %d)",
dk->dims()[dk->dims().size() - 1],
out_grad.dims()[out_grad.dims().size() - 1]));
PADDLE_ENFORCE_EQ(
dv->dims()[dv->dims().size() - 1],
out_grad.dims()[out_grad.dims().size() - 1],
common::errors::InvalidArgument(
"head dimension of dv != head dimension of out_grad (%d != %d)",
dv->dims()[dv->dims().size() - 1],
out_grad.dims()[out_grad.dims().size() - 1]));
#else
RaiseNotSupportedError();
#endif
}
} // namespace phi
PD_REGISTER_KERNEL(flash_attn_v3_grad,
GPU,
ALL_LAYOUT,
phi::FlashAttnV3GradKernel,
phi::float16,
phi::bfloat16) {}
PD_REGISTER_KERNEL(flash_attn_v3_varlen_grad,
GPU,
ALL_LAYOUT,
phi::FlashAttnV3VarlenGradKernel,
phi::float16,
phi::bfloat16) {}
PD_REGISTER_KERNEL(flashmask_attention_v2_grad,
GPU,
ALL_LAYOUT,
phi::FlashMaskV2GradKernel,
phi::float16,
phi::bfloat16) {}