// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "paddle/phi/kernels/fusion/gpu/masked_multihead_attention_kernel.h" #include "paddle/common/enforce.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/aligned_vector.h" #include "paddle/phi/kernels/fusion/gpu/mmha_util.cu.h" namespace phi { namespace fusion { #ifndef PADDLE_WITH_HIP constexpr unsigned int str2int(const char *str, int h = 0) { return !str[h] ? 5381 : (str2int(str, h + 1) * 33) ^ str[h]; } template struct Masked_multihead_attention_params { float *qk_sum_max_split_seq; float *split_out; // qkv_out, [B, 1(seq_len), 3, num_head * dim_head] const T *qkv; // bias, [3, num_head, dim_head] T *qkv_bias; // [2, B, num_head, max_seq_len(valid cache_seq_len), dim_head] // k [B, num_head, dim_head/x, max_seq_len, x], that is `seq_len` first // v [B, num_head, max_seq_len, dim_head] T *cache_kv; // [B, max_seq_len] const int *beam_cache_offset = nullptr; const int *sequence_lengths{nullptr}; // The RoPE embedding, [2, B, rotary_seq_len, 1, dim_head] // rotary_emb_dims = 1 if pos_ids_extra is null else 2 const float *rotary_emb; // TODO(wangxi): optimize with input_lengths and max_input_len? // [bsz, 1, 1, time_step(cache_seq_length)+1] const T *attn_mask; int rotary_emb_dims; int batch_size; // batch * beam int beam_width; int cache_batch_size; int num_head; // k_num_head and v_num_head must be equal, we unify them. // kv_num_head = k_num_head && kv_num_head == v_num_head int kv_num_head; int timestep; // cache_seq_length int max_seq_length; // 1.f / sqrt(Dh) float inv_sqrt_dh; int steps_per_block; int split_seq = 1; bool add_qkv_bias; bool neox_rotary_style; // whether to broadcast num_heads(2nd) dimension for attn_mask // in MMHA, if false, attn_mask shape should be // [bsz, num_heads, 1, time_step(cache_seq_length)+1] bool mask_broadcast_num_heads; }; template __global__ void masked_multihead_attention_kernel( Masked_multihead_attention_params params, LoadFunc load_func, StoreFunc store_func) { #if CUDA_ARCH_FP16_SUPPORTED(__CUDA_ARCH__) const int bi = blockIdx.z; // params.sequence_lengths[bi] means how many k and v we have cached in // cache_kv. if (params.sequence_lengths && params.sequence_lengths[bi] < 0) { return; } typedef PDDataTypeTraits traits_; typedef typename traits_::DataType DataType_; static_assert(Dh_MAX % THREADS_PER_KEY == 0, ""); static_assert(Dh_MAX % THREADS_PER_VALUE == 0, ""); constexpr int WARP_SIZE = 32; constexpr int WARPS_PER_BLOCK = THREADS_PER_BLOCK / WARP_SIZE; extern __shared__ char smem_[]; float *qk_smem = reinterpret_cast(smem_); char *logits_smem_ = smem_; // fp32 accum for logits float *logits_smem = reinterpret_cast(logits_smem_); T *out_smem = reinterpret_cast(smem_); __shared__ float red_smem[WARPS_PER_BLOCK * 2]; using Qk_vec = typename Qk_vec_::Type; using Qk_vec_RoPE = typename Qk_vec_RoPE_::Type; __shared__ __align__(sizeof(Qk_vec)) T q_smem[Dh_MAX]; // beam id const int beami = bi % params.beam_width; // real batch id const int bbi = bi / params.beam_width; const int hi = blockIdx.y; const int bhi = bi * params.num_head + hi; const int kv_num_head = params.kv_num_head; const int num_head_per_group = params.num_head / kv_num_head; // hi means the head index in query processed by this cuda thread. // kv_bhi means the merged batch and head index in key and value processed by // this cuda thread. const int kv_bhi = bi * kv_num_head + hi / num_head_per_group; const int bbhi = bbi * params.beam_width * params.num_head + hi; const int tid = threadIdx.x; const int bi_seq_len_offset = bi * params.max_seq_length; float qk_max = -FLT_MAX; float qk = 0; int act_time_step = params.sequence_lengths == nullptr ? params.timestep : params.sequence_lengths[bi]; // with SPLIT, The last single q*k*v is computed by the last threadBlock of // split_index const int split_index = blockIdx.x; int start_seq = 0; int end_seq = act_time_step; bool is_last_block = (SPLIT == false); int real_split_each_batch = (act_time_step - 1) / params.steps_per_block + 1; if constexpr (SPLIT) { if (split_index >= real_split_each_batch) return; start_seq = split_index * params.steps_per_block; end_seq = start_seq + params.steps_per_block; if (split_index == real_split_each_batch - 1) { is_last_block = true; end_seq = act_time_step; } } int curr_seq_section = end_seq - start_seq; // qkv [B, S=1, num_head + 2 * kv_num_head, head_dim] // this hi means the head index in query! int qkv_base_offset = bi * (params.num_head + 2 * kv_num_head) * Dh + hi * Dh; // QK_VEC_SIZE is only used for compute q dot k . constexpr int QK_VEC_SIZE = sizeof(Qk_vec) / sizeof(T); static_assert(Dh_MAX % QK_VEC_SIZE == 0, ""); // Use block reduction if needed // static_assert(Dh_MAX / QK_VEC_SIZE <= WARP_SIZE, ""); constexpr int QK_VECS_PER_WARP = Dh_MAX / QK_VEC_SIZE; // cache_k, [B, num_head, head_dim / x, max_seq_len, x] // x == 4/8 for FP32/FP16, 128bit, 16Byte constexpr int QK_ELTS_IN_16B = 16 / sizeof(T); constexpr int QK_VECS_IN_16B = 16 / sizeof(Qk_vec); // const T *q_base = params.qkv; // const T *k_base = params.qkv + params.num_head * Dh; T *q_bias_base = nullptr; T *k_bias_base = nullptr; if (params.add_qkv_bias) { q_bias_base = params.qkv_bias; k_bias_base = params.qkv_bias + params.num_head * Dh; } // q and k have only head_dim scalar elements. // below only compute q dot k = 1 element. // q has QK_VECS_PER_WARP elements, [Qk_vec, Qk_vec, ..., Qk_vec] // k has QK_VECS_PER_WARP elements: [Qk_vec, Qk_vec, ..., Qk_vec] // per cuda thread read a Qk_vec of q and k and compute q dot k. if (tid < QK_VECS_PER_WARP) { int qk_offset = qkv_base_offset + tid * QK_VEC_SIZE; int q_bias_offset = hi * Dh + tid * QK_VEC_SIZE; int k_bias_offset = hi / num_head_per_group * Dh + tid * QK_VEC_SIZE; Qk_vec q; zero(q); // q = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) // ? *reinterpret_cast(&q_base[qk_offset]) // : q; if (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) { load_func.template load(q, qk_offset); } Qk_vec k; zero(k); // k = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) // ? *reinterpret_cast(&k_base[qk_offset]) // : k; if ((Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) && is_last_block) { load_func.template load(k, params.num_head * Dh + qk_offset - hi * Dh + hi / num_head_per_group * Dh); } if (params.add_qkv_bias) { Qk_vec q_bias; zero(q_bias); Qk_vec k_bias; zero(k_bias); q_bias = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) ? *reinterpret_cast(&q_bias_base[q_bias_offset]) : q_bias; k_bias = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) ? *reinterpret_cast(&k_bias_base[k_bias_offset]) : k_bias; q = add(q, q_bias); // TODO(wangxi): See this https://github.com/microsoft/unilm/issues/510 // we may not require k_bias. k = add(k, k_bias); } if (!params.neox_rotary_style) { if (params.rotary_emb_dims != 0) { int rotary_offset = bi * Dh + tid * QK_VEC_SIZE; const float *cos_base = params.rotary_emb; const float *sin_base = params.rotary_emb + params.batch_size * Dh; Qk_vec_RoPE cos_emb, sin_emb; zero(cos_emb); zero(sin_emb); cos_emb = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &cos_base[rotary_offset]) : cos_emb; sin_emb = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &sin_base[rotary_offset]) : sin_emb; apply_rotary_embedding(q, k, cos_emb, sin_emb); } } else { /* old rotary pos emb */ if (params.rotary_emb_dims != 0) { int last_dim = Dh / params.rotary_emb_dims; int half_lastdim = last_dim / 2; int rotary_offset = bi * Dh + tid * QK_VEC_SIZE; const float *cos_base = params.rotary_emb; const float *sin_base = params.rotary_emb + params.batch_size * Dh; int stride = half_lastdim / QK_VEC_SIZE; int stride_all_lastdim = 2 * stride; int right_id = tid / stride_all_lastdim * stride_all_lastdim + (tid + stride) % (stride_all_lastdim); int qk_right_offset = qkv_base_offset + right_id * QK_VEC_SIZE; int q_right_bias_offset = hi * Dh + right_id * QK_VEC_SIZE; int k_right_bias_offset = hi / num_head_per_group * Dh + right_id * QK_VEC_SIZE; Qk_vec q_right; zero(q_right); // q_right = // (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) // ? *reinterpret_cast(&q_base[qk_right_offset]) // : q_right; if (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) { load_func.template load(q_right, qk_right_offset); } Qk_vec k_right; zero(k_right); // k_right = // (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) // ? *reinterpret_cast(&k_base[qk_right_offset]) // : k_right; if (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) { load_func.template load(k_right, params.num_head * Dh + qk_right_offset - hi * Dh + hi / num_head_per_group * Dh); } if (params.add_qkv_bias) { Qk_vec q_right_bias; zero(q_right_bias); q_right_bias = (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &q_bias_base[q_right_bias_offset]) : q_right_bias; Qk_vec k_right_bias; zero(k_right_bias); k_right_bias = (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &k_bias_base[k_right_bias_offset]) : k_right_bias; q_right = add(q_right, q_right_bias); k_right = add(k_right, k_right_bias); } Qk_vec_RoPE cos_emb; zero(cos_emb); cos_emb = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &cos_base[rotary_offset]) : cos_emb; Qk_vec_RoPE sin_emb; zero(sin_emb); sin_emb = (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &sin_base[rotary_offset]) : sin_emb; float alpha = (tid % stride_all_lastdim) < stride ? static_cast(-1) : static_cast(1); q = apply_rotary_emb( q, q_right, cos_emb, sin_emb, alpha); k = apply_rotary_emb( k, k_right, cos_emb, sin_emb, alpha); } } *reinterpret_cast(&q_smem[tid * QK_VEC_SIZE]) = q; if (is_last_block) { int co = tid / QK_VECS_IN_16B; int ci = (tid % QK_VECS_IN_16B) * QK_VEC_SIZE; int offset = kv_bhi * params.max_seq_length * Dh + co * params.max_seq_length * QK_ELTS_IN_16B + act_time_step * QK_ELTS_IN_16B + ci; if (Dh == Dh_MAX || co < Dh / QK_ELTS_IN_16B) { *reinterpret_cast(¶ms.cache_kv[offset]) = k; } qk = dot(q, k); // QK_VECS_PER_WARP is <= WARP_SIZE, reduce it within a warp! if (QK_VECS_PER_WARP <= WARP_SIZE) { #pragma unroll for (int mask = QK_VECS_PER_WARP / 2; mask >= 1; mask /= 2) { qk += __shfl_xor_sync(shfl_mask(QK_VECS_PER_WARP), qk, mask); } } } } // when QK_VECS_PER_WARP > WARP_SIZE, we need to reduce the qk in smem! if (QK_VECS_PER_WARP > WARP_SIZE && is_last_block) { constexpr int WARPS_PER_RED = (QK_VECS_PER_WARP + WARP_SIZE - 1) / WARP_SIZE; qk = block_sum(&red_smem[WARPS_PER_RED], qk); } // Let only the last cuda ThreadBlock compute the final q*k. if (tid == 0 && is_last_block) { // NOTE(wangxi): mask must be 0.0 // T mask = params.attn_mask[ // bi * (params.timestep + 1) + params.timestep]; // qk += static_cast(mask); qk *= params.inv_sqrt_dh; if (params.attn_mask) { auto mask_bhi = params.mask_broadcast_num_heads ? bi : bhi; T mask = params.attn_mask[mask_bhi * (params.timestep + 1) + act_time_step]; qk += static_cast(mask); } qk_max = qk; qk_smem[act_time_step - start_seq] = qk; } __syncthreads(); using K_vec = typename K_vec_::Type; constexpr int K_VEC_SIZE = sizeof(K_vec) / sizeof(T); static_assert(Dh_MAX % K_VEC_SIZE == 0, ""); constexpr int K_ELTS_PER_THREAD = Dh_MAX / THREADS_PER_KEY; constexpr int K_VECS_PER_THREAD = K_ELTS_PER_THREAD / K_VEC_SIZE; int ko = tid / THREADS_PER_KEY + start_seq; int ki = (tid % THREADS_PER_KEY) * K_VEC_SIZE; static_assert(Dh_MAX == THREADS_PER_KEY * K_VEC_SIZE * K_VECS_PER_THREAD, ""); K_vec q[K_VECS_PER_THREAD]; #pragma unroll for (int i = 0; i < K_VECS_PER_THREAD; ++i) { q[i] = *reinterpret_cast( &q_smem[ki + i * THREADS_PER_KEY * K_VEC_SIZE]); } constexpr int K_PER_ITER = THREADS_PER_BLOCK / THREADS_PER_KEY; constexpr int K_PER_WARP = WARP_SIZE / THREADS_PER_KEY; T *k_cache = ¶ms.cache_kv[kv_bhi * params.max_seq_length * Dh + ki]; T *k_cache_batch = ¶ms.cache_kv[bbhi * params.max_seq_length * Dh + ki]; int ti_end = div_up(curr_seq_section, K_PER_WARP) * K_PER_WARP + start_seq; const int *beam_offsets = params.beam_cache_offset ? ¶ms.beam_cache_offset[bi_seq_len_offset] : nullptr; #pragma unroll for (int ti = ko; ti < ti_end; ti += K_PER_ITER) { const int beam_offset = beam_offsets ? beam_offsets[ti] * params.num_head * params.max_seq_length * Dh : 0; K_vec k[K_VECS_PER_THREAD]; K_vec k_vec_zero; zero(k_vec_zero); #pragma unroll for (int ii = 0; ii < K_VECS_PER_THREAD; ++ii) { int jj = ii * params.max_seq_length + ti; if (ti < end_seq) { if (beam_offset) { k[ii] = (Dh == Dh_MAX || jj * QK_ELTS_IN_16B < Dh * params.max_seq_length) ? *reinterpret_cast( &k_cache_batch[beam_offset + jj * QK_ELTS_IN_16B]) : k_vec_zero; } else { k[ii] = (Dh == Dh_MAX || jj * QK_ELTS_IN_16B < Dh * params.max_seq_length) ? *reinterpret_cast( &k_cache[jj * QK_ELTS_IN_16B]) : k_vec_zero; } } } // NOTE(liyurui): We should multiple q with inv_sqrt_dh first, for dot(q, k) // may overflow with FP16 in large model. float qk = Qk_dot::dot(q, k, params.inv_sqrt_dh); // bool is_mask = false; if (ti < end_seq && tid % THREADS_PER_KEY == 0) { // qk_max = is_mask ? qk_max : fmaxf(qk_max, qk); auto mask_bhi = params.mask_broadcast_num_heads ? bi : bhi; // T mask = params.attn_mask[mask_bhi * (params.timestep + 1) + ti]; if (params.attn_mask) { T mask = params.attn_mask[mask_bhi * (params.timestep + 1) + ti]; qk += static_cast(mask); } qk_max = fmaxf(qk_max, qk); qk_smem[ti - start_seq] = qk; } } #pragma unroll for (int mask = WARP_SIZE / 2; mask >= THREADS_PER_KEY; mask /= 2) { qk_max = fmaxf(qk_max, __shfl_xor_sync(uint32_t(-1), qk_max, mask)); } const int warp = tid / WARP_SIZE; const int lane = tid % WARP_SIZE; if (lane == 0) { red_smem[warp] = qk_max; } __syncthreads(); qk_max = lane < WARPS_PER_BLOCK ? red_smem[lane] : -FLT_MAX; #pragma unroll for (int mask = WARPS_PER_BLOCK / 2; mask >= 1; mask /= 2) { qk_max = fmaxf(qk_max, __shfl_xor_sync(uint32_t(-1), qk_max, mask)); } qk_max = __shfl_sync(uint32_t(-1), qk_max, 0); int useful_smem_index = is_last_block ? curr_seq_section : curr_seq_section - 1; float sum = 0.f; for (int ti = tid; ti <= useful_smem_index; ti += THREADS_PER_BLOCK) { // bool is_mask = false; // float logit = is_mask ? 0.f : __expf(qk_smem[ti] - qk_max); float logit = __expf(qk_smem[ti] - qk_max); sum += logit; qk_smem[ti] = logit; } sum = block_sum(&red_smem[WARPS_PER_BLOCK], sum); int bhsi = bhi * params.split_seq; if (SPLIT && tid == 0) { float2 sum_max = {sum, qk_max}; *reinterpret_cast( ¶ms.qk_sum_max_split_seq[(bhsi + split_index) * 2]) = sum_max; } // FIXME(wangxi): need add 1.e-6f? float inv_sum = __fdividef(1.f, sum + 1.e-6f); for (int ti = tid; ti <= useful_smem_index; ti += THREADS_PER_BLOCK) { convert_from_float(logits_smem[ti], qk_smem[ti] * inv_sum); } __syncthreads(); constexpr int V_VEC_SIZE = Dh_MAX / THREADS_PER_VALUE; using V_vec = typename V_vec_::Type; // now we have got [1, seq] ,distributed in logits_smem. // next we compute [1, seq] * [seq, head_dim] = [1, head_dim] // THREADS_PER_VALUE means num of threads per value's head_dim. // we split the seq dimension for more cuda threads to compute. // vo means the first seq index processed by this cuda thread in the value. // vi means the head_dim index processed by this cuda thread in the value. // so this cuda thread compute [1, k] * [k, vi:vi+V_VEC_SIZE] and k starts // from vo and increases by a step V_PER_ITER. int vo = tid / THREADS_PER_VALUE + start_seq; int vi = (tid % THREADS_PER_VALUE) * V_VEC_SIZE; T *v_cache = ¶ms.cache_kv[params.cache_batch_size * kv_num_head * params.max_seq_length * Dh + kv_bhi * params.max_seq_length * Dh + vi]; T *v_cache_batch = ¶ms.cache_kv[params.batch_size * params.num_head * params.max_seq_length * Dh + bbhi * params.max_seq_length * Dh + vi]; #ifdef MMHA_USE_FP32_ACUM_FOR_OUT using V_vec_acum = typename V_vec_acum_fp32_::Type; #else using V_vec_acum = V_vec; #endif V_vec_acum out; zero(out); // V_PER_ITER is used to strip-mined the seq dimension. constexpr int V_PER_ITER = THREADS_PER_BLOCK / THREADS_PER_VALUE; if (Dh == Dh_MAX || vi < Dh) { #pragma unroll for (int ti = vo; ti < end_seq; ti += V_PER_ITER) { const int beam_offset = beam_offsets ? beam_offsets[ti] * params.num_head * params.max_seq_length * Dh : 0; V_vec v; if (beam_offset) { v = *reinterpret_cast( &v_cache_batch[beam_offset + ti * Dh]); } else { v = *reinterpret_cast(&v_cache[ti * Dh]); } #if defined(MMHA_USE_FP32_ACUM_FOR_LOGITS) float logit = logits_smem[ti - start_seq]; out = fma(logit, cast_to_float(v), out); #else DataType_ logit = static_cast(logits_smem[ti - start_seq]); // Update the partial sums. out = fma(logit, v, out); #endif } } V_vec v_bias; zero(v_bias); // now we process the last v. if (vo == (act_time_step % V_PER_ITER + start_seq) && (Dh == Dh_MAX || vi < Dh) && is_last_block) { // V_vec v = *reinterpret_cast( // ¶ms.qkv[2 * params.num_head * Dh + qkv_base_offset + vi]); V_vec v; load_func.template load(v, qkv_base_offset + vi - hi * Dh + params.num_head * Dh + kv_num_head * Dh + hi / num_head_per_group * Dh); if (params.add_qkv_bias) { v_bias = *reinterpret_cast( ¶ms.qkv_bias[(kv_num_head + params.num_head) * Dh + hi / num_head_per_group * Dh + vi]); v = add(v, v_bias); } *reinterpret_cast(&v_cache[act_time_step * Dh]) = v; #if defined(MMHA_USE_FP32_ACUM_FOR_LOGITS) out = fma(logits_smem[act_time_step - start_seq], cast_to_float(v), out); #else out = fma(logits_smem[act_time_step - start_seq], v, out); #endif } __syncthreads(); // now we do the reduction in the seq dimension to get [1, head_dim]. if (Dh == Dh_MAX || vi < Dh) { int vo_blk = vo - start_seq; // vo id of current block #pragma unroll for (int active_groups = V_PER_ITER; active_groups >= 2; active_groups /= 2) { int midpoint = active_groups / 2; if (vo_blk >= midpoint && vo_blk < active_groups && (Dh == Dh_MAX || vi < Dh)) { #ifdef MMHA_USE_FP32_ACUM_FOR_OUT convert_from_float(*reinterpret_cast( &out_smem[(vo_blk - midpoint) * Dh + vi]), out); #else *reinterpret_cast(&out_smem[(vo_blk - midpoint) * Dh + vi]) = out; #endif } __syncthreads(); if (vo_blk < midpoint && (Dh == Dh_MAX || vi < Dh)) { out = add(*reinterpret_cast(&out_smem[vo_blk * Dh + vi]), out); } __syncthreads(); } } if (vo == start_seq && (Dh == Dh_MAX || vi < Dh)) { if (SPLIT && real_split_each_batch > 1) { #ifdef MMHA_USE_FP32_ACUM_FOR_OUT *(reinterpret_cast( ¶ms.split_out[(bhsi + split_index) * Dh + vi])) = out; #else *(reinterpret_cast::Type *>( ¶ms.split_out[(bhsi + split_index) * Dh + vi])) = cast_to_float(out); #endif } else { #ifdef MMHA_USE_FP32_ACUM_FOR_OUT V_vec tmp_out; convert_from_float(tmp_out, out); store_func.template store(tmp_out, bhi * Dh + vi); #else store_func.template store(out, bhi * Dh + vi); #endif } } #else assert(false); #endif } template __global__ void post_process_kernel(Masked_multihead_attention_params params, StoreFunc store_func) { const int bi = blockIdx.y; int act_time_step = params.sequence_lengths == nullptr ? params.timestep : params.sequence_lengths[bi]; int real_split_each_batch = (act_time_step - 1) / params.steps_per_block + 1; if (real_split_each_batch <= 1) { return; } const int tid = threadIdx.x; const int hi = blockIdx.x; const int bhi = (bi * params.num_head + hi); const int bhsi = (bi * params.num_head + hi) * params.split_seq; extern __shared__ float2 qk_sum_max_smem[]; for (int i = tid; i < real_split_each_batch; i += blockDim.x) { qk_sum_max_smem[i] = *reinterpret_cast( ¶ms.qk_sum_max_split_seq[(bhsi + i) * 2]); } __syncthreads(); float max = -FLT_MAX; float sum = 0; float v = 0; if (tid < Dh) { #pragma unroll for (int i = 0; i < real_split_each_batch; ++i) { float2 sum_max = qk_sum_max_smem[i]; float tmp_max = sum_max.y; max = tmp_max > max ? tmp_max : max; } #pragma unroll for (int i = 0; i < real_split_each_batch; ++i) { float2 sum_max = qk_sum_max_smem[i]; // split_out:[bsz , num_head, split_seq, dim_head] float this_v = params.split_out[(bhsi + i) * Dh + tid]; float real_this_sum = sum_max.x * __expf(sum_max.y - max); v += real_this_sum * this_v; sum += real_this_sum; } v /= sum; T tmp_v = (T)v; store_func.template store(tmp_v, bhi * Dh + tid); // params.out[bhi * Dh + tid] = (T)(v); } } template inline size_t smem_size_in_bytes( const Masked_multihead_attention_params ¶ms, int dim_head, int threads_per_value, int threads_per_block) { // for qk_smem and logits_smem(both float) size_t qk_sz = div_up(params.timestep, 4) * 16; if (SPLIT) { qk_sz = div_up(params.steps_per_block, 4) * 16; } // for reduce (logits dot V) result int rows_per_red = threads_per_block / threads_per_value; size_t red_sz = rows_per_red * dim_head * sizeof(T) / 2; return max(qk_sz, red_sz); } #define MMHA_LAUNCH_KERNEL(T, \ Dh, \ Dh_MAX, \ THDS_PER_KEY, \ THDS_PER_VALUE, \ THDS_PER_BLOCK, \ stream, \ load_func, \ store_func) \ size_t smem_sz_size = smem_size_in_bytes( \ params, Dh, THDS_PER_VALUE, THDS_PER_BLOCK); \ PADDLE_ENFORCE_LE_INT_MAX(smem_sz_size, \ "masked_multihead_attention shared memory size"); \ int smem_sz = static_cast(smem_sz_size); \ constexpr auto kernel_fn = \ masked_multihead_attention_kernel; \ if (smem_sz > 0xc000) { \ cudaFuncSetAttribute( \ kernel_fn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_sz); \ } \ dim3 grid(params.split_seq, params.num_head, params.batch_size); \ kernel_fn<<>>( \ params, load_func, store_func) template void fmha_launch_kernel(const Masked_multihead_attention_params ¶ms, const cudaStream_t &stream, LoadFunc load_func, StoreFunc store_func) { constexpr int THREADS_PER_VALUE = Dh_MAX * sizeof(T) / 16; // If try adjusting the hyperparam, THDS_PER_KEY can try [1, 2, 4] // for LLM: multiBatch(8)/longSeq(>2048) case, reduce THDS_PER_KEY may work // for super longSeq(>3072) case, larger steps_per_block(256) may work if constexpr (SPLIT) { MMHA_LAUNCH_KERNEL(T, Dh, Dh_MAX, 4, THREADS_PER_VALUE, 128, stream, load_func, store_func); } else { if (params.timestep < 32) { MMHA_LAUNCH_KERNEL(T, Dh, Dh_MAX, 4, THREADS_PER_VALUE, 64, stream, load_func, store_func); } else if (params.timestep < 2048) { #if defined(MMHA_USE_HMMA_FOR_REDUCTION) && defined(__CUDA_ARCH__) && \ __CUDA_ARCH__ >= 750 MMHA_LAUNCH_KERNEL(T, Dh, Dh_MAX, 4, THREADS_PER_VALUE, 256, stream, load_func, store_func); #else MMHA_LAUNCH_KERNEL(T, Dh, Dh_MAX, 2, THREADS_PER_VALUE, 128, stream, load_func, store_func); #endif } else { MMHA_LAUNCH_KERNEL(T, Dh, Dh_MAX, 1, THREADS_PER_VALUE, 256, stream, load_func, store_func); } } } #define FMHA_LAUNCH_KERNEL(dim_head_, dim_head_max_, stream) \ case dim_head_: \ fmha_launch_kernel(params, stream, load_func, store_func); \ if (SPLIT) { \ post_process_kernel \ <<>>(params, store_func); \ } \ break; template void fmha_impl(const GPUContext &dev_ctx, const Masked_multihead_attention_params ¶ms, int dim_head, LoadFunc load_func, StoreFunc store_func) { dim3 grid(params.num_head, params.batch_size); int smem_sz = params.split_seq * sizeof(float2); auto stream = dev_ctx.stream(); switch (dim_head) { FMHA_LAUNCH_KERNEL(16, 32, stream) FMHA_LAUNCH_KERNEL(32, 32, stream) FMHA_LAUNCH_KERNEL(64, 64, stream) FMHA_LAUNCH_KERNEL(80, 128, stream) FMHA_LAUNCH_KERNEL(96, 128, stream) FMHA_LAUNCH_KERNEL(128, 128, stream) FMHA_LAUNCH_KERNEL(192, 256, stream) default: PADDLE_THROW(common::errors::Unimplemented( "Dim_head = %d is unsupported!", dim_head)); } } template void DispatchFMHA(const GPUContext &dev_ctx, const DenseTensor &qkv_tensor, const Masked_multihead_attention_params ¶ms, int num_head, int dim_head, DenseTensor *out_tensor, const DenseTensor *dequant_qkv_scales = nullptr, const float quant_fmha_out_scale = -1, const int quant_round_type = 1, const float quant_max_bound = 127.0f, const float quant_min_bound = -127.0f) { if (dequant_qkv_scales != nullptr && quant_fmha_out_scale > 0) { MMHALoad load_func(qkv_tensor.data(), dequant_qkv_scales->data(), 3 * num_head * dim_head); MMHAStore store_func(out_tensor->data(), quant_round_type, quant_fmha_out_scale, quant_max_bound, quant_min_bound); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } else if (dequant_qkv_scales == nullptr && quant_fmha_out_scale > 0) { MMHALoad load_func(qkv_tensor.data()); MMHAStore store_func(out_tensor->data(), quant_round_type, quant_fmha_out_scale, quant_max_bound, quant_min_bound); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } else if (dequant_qkv_scales != nullptr && quant_fmha_out_scale <= 0) { MMHALoad load_func(qkv_tensor.data(), dequant_qkv_scales->data(), 3 * num_head * dim_head); MMHAStore store_func(out_tensor->data()); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } else { MMHALoad load_func(qkv_tensor.data()); MMHAStore store_func(out_tensor->data()); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } } template void DispatchFMHA(const GPUContext &dev_ctx, const DenseTensor &qkv_tensor, const DenseTensor &shift, const DenseTensor &smooth, const Masked_multihead_attention_params ¶ms, int num_head, int dim_head, DenseTensor *out_tensor, const DenseTensor *dequant_qkv_scales = nullptr, const float quant_fmha_out_scale = -1, const int quant_round_type = 1, const float quant_max_bound = 127.0f, const float quant_min_bound = -127.0f) { if (dequant_qkv_scales != nullptr && quant_fmha_out_scale > 0) { MMHALoad load_func(qkv_tensor.data(), dequant_qkv_scales->data(), 3 * num_head * dim_head); MMHAStore store_func(out_tensor->data(), shift.data(), smooth.data(), num_head * dim_head, quant_round_type, quant_fmha_out_scale, quant_max_bound, quant_min_bound); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } else if (dequant_qkv_scales == nullptr && quant_fmha_out_scale > 0) { MMHALoad load_func(qkv_tensor.data()); MMHAStore store_func(out_tensor->data(), shift.data(), smooth.data(), num_head * dim_head, quant_round_type, quant_fmha_out_scale, quant_max_bound, quant_min_bound); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } else if (dequant_qkv_scales != nullptr && quant_fmha_out_scale <= 0) { MMHALoad load_func(qkv_tensor.data(), dequant_qkv_scales->data(), 3 * num_head * dim_head); MMHAStore store_func(out_tensor->data(), shift.data(), smooth.data(), num_head * dim_head); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } else { MMHALoad load_func(qkv_tensor.data()); MMHAStore store_func(out_tensor->data(), shift.data(), smooth.data(), num_head * dim_head); fmha_impl( dev_ctx, params, dim_head, load_func, store_func); } } struct NormalVersion {}; struct UnusedVersion {}; template struct DispatchDtypeTrait { using FuncVersion = NormalVersion; }; template <> struct DispatchDtypeTrait { using FuncVersion = UnusedVersion; }; template void DispatchWithDtype(const Context &dev_ctx, const DenseTensor &x, const DenseTensor &cache_kv, const optional &bias, const optional &src_mask, const optional &cum_offsets, const optional &sequence_lengths, const optional &rotary_tensor, const optional &beam_cache_offset, const optional &qkv_out_scale, const optional &out_shift, const optional &out_smooth, int seq_len, int rotary_emb_dims, const bool use_neox_rotary_style, const float out_scale, const int quant_round_type, const float quant_max_bound, const float quant_min_bound, DenseTensor *out, DenseTensor *cache_kv_out, DenseTensor *beam_cache_offset_out, NormalVersion) { const auto &x_dims = x.dims(); int bsz = x_dims[0]; int64_t cache_bsz = cache_kv.dims()[1]; // TODO(large-tensor): downstream functors may still use int int64_t max_seq_len = cache_kv.dims()[3]; // TODO(large-tensor): downstream functors may still use int int64_t dim_head = cache_kv.dims()[4]; // TODO(large-tensor): downstream functors may still use int int timestep = max_seq_len; float inv_sqrt_dh = 1. / sqrt(dim_head); int64_t k_num_head = cache_kv.dims()[2]; // TODO(large-tensor): downstream functors may still use int int v_num_head = k_num_head; // this num_head means query's head int num_head = x.dims()[x.dims().size() - 1] / dim_head - k_num_head - v_num_head; Masked_multihead_attention_params params; bool mask_broadcast_num_heads = true; params.add_qkv_bias = false; if (bias) { params.add_qkv_bias = true; params.qkv_bias = const_cast(bias->data()); } if (src_mask) { if (src_mask->dims()[1] == 1) { mask_broadcast_num_heads = true; } else if (src_mask->dims()[1] == num_head) { mask_broadcast_num_heads = false; } else { PADDLE_THROW(errors::InvalidArgument( "Unknown dimension for attn_mask, the num_head(2nd) " "dimension is invalid, it should be 1 or num_head(%d), " "but got %d", num_head, src_mask->dims()[1])); } params.attn_mask = src_mask->data(); timestep = src_mask->dims()[3] - 1; } if (out_scale > 0) { dev_ctx.template Alloc(out); } else { dev_ctx.template Alloc(out); } if (sequence_lengths) { params.sequence_lengths = sequence_lengths->data(); } if (cum_offsets) { PADDLE_THROW(common::errors::PermissionDenied( "Current mmha kernel does not support cum_offsets param.")); } if (rotary_emb_dims > 0) { params.rotary_emb = rotary_tensor->data(); } else { params.rotary_emb = nullptr; } if (beam_cache_offset) { params.beam_cache_offset = beam_cache_offset->data(); params.beam_width = beam_cache_offset->dims()[1]; } params.mask_broadcast_num_heads = mask_broadcast_num_heads; params.cache_kv = const_cast(cache_kv_out->data()); params.neox_rotary_style = use_neox_rotary_style; params.batch_size = bsz; params.cache_batch_size = cache_bsz; params.num_head = num_head; params.kv_num_head = k_num_head; params.timestep = timestep; params.max_seq_length = max_seq_len; params.inv_sqrt_dh = inv_sqrt_dh; params.rotary_emb_dims = rotary_emb_dims; params.steps_per_block = timestep; // if not SPLIT, this is useless. params.split_seq = 1; // if not SPLIT, grid.x==1 bool SPLIT = false; if (bsz <= 4 && timestep >= 512) { SPLIT = true; } if (SPLIT) { const int steps_per_block = 128; params.steps_per_block = steps_per_block; params.split_seq = (timestep - 1) / steps_per_block + 1; int split_seq = params.split_seq; DenseTensor qk_sum_max_split_seq; // 2 means sum and max. qk_sum_max_split_seq.Resize({bsz, num_head, split_seq, 2}); dev_ctx.template Alloc(&qk_sum_max_split_seq, qk_sum_max_split_seq.numel() * sizeof(float)); params.qk_sum_max_split_seq = qk_sum_max_split_seq.data(); DenseTensor split_out; split_out.Resize({bsz, num_head, split_seq, dim_head}); dev_ctx.template Alloc(&split_out, split_out.numel() * sizeof(float)); params.split_out = split_out.data(); if (out_shift) { DispatchFMHA(dev_ctx, x, *(out_shift.get_ptr()), *(out_smooth.get_ptr()), params, num_head, dim_head, out, qkv_out_scale.get_ptr(), out_scale, quant_round_type, quant_max_bound, quant_min_bound); } else { DispatchFMHA(dev_ctx, x, params, num_head, dim_head, out, qkv_out_scale.get_ptr(), out_scale, quant_round_type, quant_max_bound, quant_min_bound); } } else { if (out_shift) { DispatchFMHA(dev_ctx, x, *(out_shift.get_ptr()), *(out_smooth.get_ptr()), params, num_head, dim_head, out, qkv_out_scale.get_ptr(), out_scale, quant_round_type, quant_max_bound, quant_min_bound); } else { DispatchFMHA(dev_ctx, x, params, num_head, dim_head, out, qkv_out_scale.get_ptr(), out_scale, quant_round_type, quant_max_bound, quant_min_bound); } } } template void DispatchWithDtype(const Context &dev_ctx, const DenseTensor &x, const DenseTensor &cache_kv, const optional &bias, const optional &src_mask, const optional &cum_offsets, const optional &sequence_lengths, const optional &rotary_tensor, const optional &beam_cache_offset, const optional &qkv_out_scale, const optional &out_shift, const optional &out_smooth, int seq_len, int rotary_emb_dims, const bool use_neox_rotary_style, const float out_scale, const int quant_round_type, const float quant_max_bound, const float quant_min_bound, DenseTensor *out, DenseTensor *cache_kv_out, DenseTensor *beam_cache_offset_out, UnusedVersion) {} #endif // PADDLE_WITH_HIP template void MMHAKernel(const Context &dev_ctx, const DenseTensor &x, const DenseTensor &cache_kv, const optional &bias, const optional &src_mask, const optional &cum_offsets, const optional &sequence_lengths, const optional &rotary_tensor, const optional &beam_cache_offset, const optional &qkv_out_scale, const optional &out_shift, const optional &out_smooth, int seq_len, int rotary_emb_dims, const bool use_neox_rotary_style, const std::string &compute_dtype, const float out_scale, const int quant_round_type, const float quant_max_bound, const float quant_min_bound, DenseTensor *out, DenseTensor *cache_kv_out, DenseTensor *beam_cache_offset_out) { #ifndef PADDLE_WITH_HIP if (x.dtype() == phi::DataType::INT32) { switch (str2int(compute_dtype.c_str())) { case str2int("fp16"): DispatchWithDtype( dev_ctx, x, cache_kv, bias, src_mask, cum_offsets, sequence_lengths, rotary_tensor, beam_cache_offset, qkv_out_scale, out_shift, out_smooth, seq_len, rotary_emb_dims, use_neox_rotary_style, out_scale, quant_round_type, quant_max_bound, quant_min_bound, out, cache_kv_out, beam_cache_offset_out, typename DispatchDtypeTrait::FuncVersion{}); break; #if CUDA_VERSION >= 11000 case str2int("bf16"): DispatchWithDtype( dev_ctx, x, cache_kv, bias, src_mask, cum_offsets, sequence_lengths, rotary_tensor, beam_cache_offset, qkv_out_scale, out_shift, out_smooth, seq_len, rotary_emb_dims, use_neox_rotary_style, out_scale, quant_round_type, quant_max_bound, quant_min_bound, out, cache_kv_out, beam_cache_offset_out, typename DispatchDtypeTrait::FuncVersion{}); break; #endif case str2int("fp32"): DispatchWithDtype( dev_ctx, x, cache_kv, bias, src_mask, cum_offsets, sequence_lengths, rotary_tensor, beam_cache_offset, qkv_out_scale, out_shift, out_smooth, seq_len, rotary_emb_dims, use_neox_rotary_style, out_scale, quant_round_type, quant_max_bound, quant_min_bound, out, cache_kv_out, beam_cache_offset_out, typename DispatchDtypeTrait::FuncVersion{}); break; default: PADDLE_THROW(common::errors::InvalidArgument( "In the case of quantization enabled with Input(x) INT32, " "Attr(compute_dtype) must be set in (bf16, fp16, fp32), " "but get compute_dtype (%s)", compute_dtype)); } } else { DispatchWithDtype( dev_ctx, x, cache_kv, bias, src_mask, cum_offsets, sequence_lengths, rotary_tensor, beam_cache_offset, qkv_out_scale, out_shift, out_smooth, seq_len, rotary_emb_dims, use_neox_rotary_style, out_scale, quant_round_type, quant_max_bound, quant_min_bound, out, cache_kv_out, beam_cache_offset_out, typename DispatchDtypeTrait::FuncVersion{}); } #endif // PADDLE_WITH_HIP } } // namespace fusion } // namespace phi #if CUDA_VERSION >= 11000 PD_REGISTER_KERNEL(masked_multihead_attention, GPU, ALL_LAYOUT, phi::fusion::MMHAKernel, float, phi::float16, phi::bfloat16, int32_t) {} #else PD_REGISTER_KERNEL(masked_multihead_attention, GPU, ALL_LAYOUT, phi::fusion::MMHAKernel, float, phi::float16, int32_t) {} #endif