// 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/common/flags.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/common/memory_utils.h" #include "paddle/phi/kernels/funcs/quant_dequant.h" #include "paddle/phi/kernels/fusion/gpu/mmha_util.cu.h" COMMON_DECLARE_bool(use_xqa_optim); COMMON_DECLARE_bool(blha_use_fp32_qk_sum); #ifdef PADDLE_WITH_HIP #define GPU(str) hip##str #define GPUMultiProcessorCount hipDeviceAttributeMultiprocessorCount #else #define GPU(str) cuda##str #define GPUMultiProcessorCount cudaDevAttrMultiProcessorCount #endif namespace phi { namespace fusion { constexpr int VEC_16B = 16; template struct Block_AttN_params { // output buffer, [B, 1(seq_len), q_num_head * dim_head] T *out; // qkv_out, [B, 1(seq_len), (2 * kv_num_head + q_num_head) * dim_head] const T *qkv; // bias, [(2 * kv_num_head + q_num_head), dim_head] const T *qkv_bias; // [bsz, seq_len] const int *cum_offsets; // TODO(wangxi): optimize with input_lengths and max_input_len? // [bsz, 1, 1, time_step(cache_seq_length)+1] const T *attn_mask; // mask_length is the 3th dimension of attn_mask. int mask_length; bool mask_broadcast_num_heads; // k_cache [max_block_num, num_head, block_size, head_size] // v_cache [max_block_num, num_head, block_size, head_size] T *k_cache; T *v_cache; uint8_t *k_cache_I; uint8_t *v_cache_I; const int *block_tables; const int *sequence_lengths{nullptr}; const float *rotary_emb = nullptr; int rotary_emb_dims; int rope_stride; float inv_compression_ratio = 1.0f; // Default as 1.0 int batch_size; // batch * beam int beam_width = 1; int max_num_blocks_per_seq; int block_size; int q_num_head; int kv_num_head; int gqa_num_per_partitions; int timestep; int seq_len; int pre_cache_length = 0; // 1.f / sqrt(Dh) float inv_sqrt_dh; bool add_qkv_bias; bool neox_rotary_style; const float *cache_k_quant_scales = nullptr; const float *cache_v_quant_scales = nullptr; const float *cache_k_dequant_scales = nullptr; const float *cache_v_dequant_scales = nullptr; float rope_theta = 10000.0f; }; template __global__ __launch_bounds__(THREADS_PER_BLOCK) void block_attention_kernel( Block_AttN_params params, LoadFunc load_func, StoreFunc store_func) { #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) const int bi = blockIdx.y; int act_time_step = params.sequence_lengths[bi]; if (act_time_step == 0) { return; } act_time_step += params.pre_cache_length; const int *block_table = params.block_tables + bi * params.max_num_blocks_per_seq; 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_[]; int block_smem_offset = div_up(params.max_num_blocks_per_seq, 4) * 4 * sizeof(int); float *qk_smem = reinterpret_cast(smem_ + block_smem_offset); float *logits_smem = reinterpret_cast(smem_ + block_smem_offset); /* Note: This shared memory is used in Final Reduce, so we do not need to add offset. Since in Final Reduce Phase, the block tables is not used. */ T *out_smem = reinterpret_cast(smem_); __shared__ float red_smem[WARPS_PER_BLOCK * 2]; using Qk_vec = typename Qk_vec_::Type; using Qk_sum_type = typename Qk_vec_::Type; using Qk_vec_RoPE = typename Qk_vec_RoPE_::Type; using QK_Packed_Int8_t = typename Packed_Int8_::Type; // Each block has a head's q value __shared__ __align__(sizeof(Qk_vec)) T q_smem[Dh_MAX]; const int tid = threadIdx.x; const int hi = blockIdx.x; // head index const int kv_hi = hi / params.gqa_num_per_partitions; float k_quant_scale; float v_quant_scale; float k_dequant_scale; float v_dequant_scale; if (CACHE_TYPE == CacheType::INT8) { k_quant_scale = static_cast(params.cache_k_quant_scales[kv_hi]); v_quant_scale = static_cast(params.cache_v_quant_scales[kv_hi]); k_dequant_scale = static_cast(params.cache_k_dequant_scales[kv_hi]); v_dequant_scale = static_cast(params.cache_v_dequant_scales[kv_hi]); } const int bhi = bi * params.q_num_head + hi; const int ti = params.cum_offsets ? bi * params.seq_len - params.cum_offsets[bi] : -1; const int thi = params.cum_offsets ? ti * params.q_num_head + hi : -1; int *block_table_smem = reinterpret_cast(smem_); for (int local_id = tid; local_id < params.max_num_blocks_per_seq; local_id += blockDim.x) { block_table_smem[local_id] = block_table[local_id]; } __syncthreads(); float qk_max = -FLT_MAX; float qk = 0; const int block_idx = act_time_step / BLOCK_SIZE; const int block_offset = act_time_step % BLOCK_SIZE; const int physical_block_number = block_table_smem[block_idx]; // cache offset of current token const int base_cache_offset = physical_block_number * params.kv_num_head * BLOCK_SIZE * Dh + kv_hi * BLOCK_SIZE * Dh + block_offset * Dh; // qkv [B, S=1, num_head + 2 * (kv_num_head), head_dim] int qkv_base_offset = bi * (params.q_num_head + params.kv_num_head * 2) * Dh; 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_bias_base = nullptr; const T *k_bias_base = nullptr; if (params.add_qkv_bias) { q_bias_base = params.qkv_bias; k_bias_base = params.qkv_bias + params.q_num_head * Dh; } // Load the current timestep's Q and K, then compute q*k, // with each block computing one head. if (tid < QK_VECS_PER_WARP) { const int qk_offset = qkv_base_offset + tid * QK_VEC_SIZE; Qk_vec q; zero(q); if (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) { load_func.template load(q, qk_offset + hi * Dh); } Qk_vec k; zero(k); if (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) { load_func.template load( k, params.q_num_head * Dh + qk_offset + kv_hi * Dh); } if (params.add_qkv_bias) { const int q_bias_offset = hi * Dh + tid * QK_VEC_SIZE; const int k_bias_offset = kv_hi * Dh + tid * QK_VEC_SIZE; 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); k = add(k, k_bias); } if (params.rotary_emb_dims != 0) { if (!params.neox_rotary_style) { apply_rotary_embedding(q, k, tid, Dh, act_time_step - params.pre_cache_length, params.inv_compression_ratio, params.rope_theta); } else { int last_dim = Dh / params.rotary_emb_dims; int half_lastdim = last_dim / 2; int rotary_offset = act_time_step * Dh + tid * QK_VEC_SIZE; const float *cos_base = params.rotary_emb; const float *sin_base = params.rotary_emb + params.rope_stride; 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 q_right_offset = qkv_base_offset + hi * Dh + right_id * QK_VEC_SIZE; int k_right_offset = qkv_base_offset + params.q_num_head * Dh + kv_hi * Dh + right_id * QK_VEC_SIZE; Qk_vec q_right; zero(q_right); if (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) { load_func.template load(q_right, q_right_offset); } Qk_vec k_right; zero(k_right); if (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) { load_func.template load(k_right, k_right_offset); } 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 (Dh == Dh_MAX || tid * QK_VEC_SIZE < Dh) { if (CACHE_TYPE == CacheType::INT8) { const int offset = base_cache_offset + tid * QK_VEC_SIZE; QK_Packed_Int8_t k_tmp = round_tmp( mul(k_quant_scale, k)); *reinterpret_cast(¶ms.k_cache_I[offset]) = k_tmp; } else { const int offset = base_cache_offset + tid * QK_VEC_SIZE; *reinterpret_cast(¶ms.k_cache[offset]) = k; } } qk = dot(q, k); if (QK_VECS_PER_WARP <= WARP_SIZE) { #pragma unroll for (int mask = QK_VECS_PER_WARP / 2; mask >= 1; mask /= 2) { #ifdef PADDLE_WITH_HIP qk += __shfl_xor(qk, mask); #else qk += __shfl_xor_sync(shfl_mask(QK_VECS_PER_WARP), qk, mask); #endif } } } if (QK_VECS_PER_WARP > WARP_SIZE) { constexpr int WARPS_PER_RED = (QK_VECS_PER_WARP + WARP_SIZE - 1) / WARP_SIZE; qk = block_sum(&red_smem[WARPS_PER_RED], qk); } if (tid == 0) { qk *= params.inv_sqrt_dh; if (params.attn_mask) { auto mask_bhi = bhi; if (params.mask_broadcast_num_heads) { mask_bhi = bi; } T mask = params.attn_mask[mask_bhi * params.mask_length + act_time_step]; qk += static_cast(mask); } qk_max = qk; qk_smem[act_time_step] = qk; } __syncthreads(); using K_vec = typename K_vec_bttn_::Type; using K_vec_I = typename K_vec_I_bttn_::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; 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]); } // threads in one block can process 'K_PER_ITER' keys constexpr int K_PER_ITER = THREADS_PER_BLOCK / THREADS_PER_KEY; // threads in one warp can process 'K_PER_ITER' keys constexpr int K_PER_WARP = WARP_SIZE / THREADS_PER_KEY; int ti_end = div_up(act_time_step, K_PER_WARP) * K_PER_WARP; K_vec k[K_VECS_PER_THREAD]; K_vec k_vec_zero; zero(k_vec_zero); // ti is the timestep index for (int ti = ko; ti < ti_end; ti += K_PER_ITER) { const int physical_block_number = block_table_smem[ti / BLOCK_SIZE]; const int block_offset = ti % BLOCK_SIZE; const int k_offset = physical_block_number * params.kv_num_head * BLOCK_SIZE * Dh + kv_hi * BLOCK_SIZE * Dh + block_offset * Dh + ki; #pragma unroll for (int ii = 0; ii < K_VECS_PER_THREAD; ++ii) { if (ti < act_time_step) { if (CACHE_TYPE == CacheType::INT8) { mul_pointer_v2( &k[ii], k_dequant_scale, reinterpret_cast(params.k_cache_I + k_offset + ii * THREADS_PER_KEY * K_VEC_SIZE)); } else { k[ii] = (Dh == Dh_MAX || ii * THREADS_PER_KEY * K_VEC_SIZE < Dh) ? *reinterpret_cast( params.k_cache + k_offset + ii * THREADS_PER_KEY * K_VEC_SIZE) : k_vec_zero; } } else { k[ii] = k_vec_zero; } } float qk = Qk_dot::dot(q, k, params.inv_sqrt_dh); if (params.attn_mask) { auto mask_bhi = bhi; if (params.mask_broadcast_num_heads) { mask_bhi = bi; } T mask = params.attn_mask[mask_bhi * params.mask_length + ti]; qk += static_cast(mask); } if (ti < act_time_step && tid % THREADS_PER_KEY == 0) { qk_max = fmaxf(qk_max, qk); qk_smem[ti] = qk; } } #pragma unroll for (int mask = WARP_SIZE / 2; mask >= THREADS_PER_KEY; mask /= 2) { #ifdef PADDLE_WITH_HIP qk_max = fmaxf(qk_max, __shfl_xor(qk_max, mask)); #else qk_max = fmaxf(qk_max, __shfl_xor_sync(uint32_t(-1), qk_max, mask)); #endif } const int warp = tid / WARP_SIZE; const int lane = tid % WARP_SIZE; if (lane == 0) { red_smem[warp] = qk_max; } __syncthreads(); #ifdef PADDLE_WITH_HIP qk_max = -FLT_MAX; qk_max = red_smem[lane]; #else qk_max = lane < WARPS_PER_BLOCK ? red_smem[lane] : -FLT_MAX; #endif #pragma unroll for (int mask = WARPS_PER_BLOCK / 2; mask >= 1; mask /= 2) { #ifdef PADDLE_WITH_HIP qk_max = fmaxf(qk_max, __shfl_xor(qk_max, mask)); #else qk_max = fmaxf(qk_max, __shfl_xor_sync(uint32_t(-1), qk_max, mask)); #endif } #ifdef PADDLE_WITH_HIP qk_max = __shfl(qk_max, 0); #else qk_max = __shfl_sync(uint32_t(-1), qk_max, 0); #endif float sum = 0.f; for (int ti = tid; ti <= act_time_step; ti += THREADS_PER_BLOCK) { float logit = __expf(qk_smem[ti] - qk_max); sum += logit; qk_smem[ti] = logit; } sum = block_sum(&red_smem[WARPS_PER_BLOCK], sum); float inv_sum = __fdividef(1.f, sum + 1.e-6f); for (int ti = tid; ti <= act_time_step; 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; using V_Packed_Int8_t = typename Packed_Int8_::Type; int vo = tid / THREADS_PER_VALUE; int vi = (tid % THREADS_PER_VALUE) * V_VEC_SIZE; #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); constexpr int V_PER_ITER = THREADS_PER_BLOCK / THREADS_PER_VALUE; if (Dh == Dh_MAX || vi < Dh) { for (int ti = vo; ti < act_time_step; ti += V_PER_ITER) { int physical_block_number = block_table_smem[ti / BLOCK_SIZE]; const int block_offset = ti % BLOCK_SIZE; const int v_offset = physical_block_number * params.kv_num_head * BLOCK_SIZE * Dh + kv_hi * BLOCK_SIZE * Dh + block_offset * Dh + vi; V_vec v; if (CACHE_TYPE == CacheType::INT8) { mul_pointer_v2( &v, v_dequant_scale, reinterpret_cast(params.v_cache_I + v_offset)); } else { v = *reinterpret_cast(params.v_cache + v_offset); } #if defined(MMHA_USE_FP32_ACUM_FOR_LOGITS) float logit = logits_smem[ti]; out = fma(logit, cast_to_float(v), out); #else DataType_ logit = static_cast(logits_smem[ti]); // Update the partial sums. out = fma(logit, v, out); #endif } } V_vec v_bias; zero(v_bias); if (vo == (act_time_step % V_PER_ITER) && (Dh == Dh_MAX || vi < Dh)) { V_vec v; load_func.template load( v, (params.q_num_head + params.kv_num_head) * Dh + qkv_base_offset + kv_hi * Dh + vi); if (params.add_qkv_bias) { v_bias = *reinterpret_cast( ¶ms.qkv_bias[(params.q_num_head + params.kv_num_head) * Dh + kv_hi * Dh + vi]); v = add(v, v_bias); } if (CACHE_TYPE == CacheType::INT8) { V_Packed_Int8_t v_tmp = round_tmp( mul(v_quant_scale, v)); *reinterpret_cast(params.v_cache_I + base_cache_offset + vi) = v_tmp; } else { *reinterpret_cast(params.v_cache + base_cache_offset + vi) = v; } #if defined(MMHA_USE_FP32_ACUM_FOR_LOGITS) out = fma(logits_smem[act_time_step], cast_to_float(v), out); #else out = fma(logits_smem[act_time_step], v, out); #endif } __syncthreads(); if (Dh == Dh_MAX || vi < Dh) { #pragma unroll for (int active_groups = V_PER_ITER; active_groups >= 2; active_groups /= 2) { int midpoint = active_groups / 2; if (vo >= midpoint && vo < active_groups && (Dh == Dh_MAX || vi < Dh)) { #ifdef MMHA_USE_FP32_ACUM_FOR_OUT convert_from_float( *reinterpret_cast(&out_smem[(vo - midpoint) * Dh + vi]), out); #else *reinterpret_cast(&out_smem[(vo - midpoint) * Dh + vi]) = out; #endif } __syncthreads(); if (vo < midpoint && (Dh == Dh_MAX || vi < Dh)) { out = add(*reinterpret_cast(&out_smem[vo * Dh + vi]), out); } __syncthreads(); } } if (vo == 0 && (Dh == Dh_MAX || vi < Dh)) { #ifdef MMHA_USE_FP32_ACUM_FOR_OUT V_vec tmp_out; convert_from_float(tmp_out, out); store_func.template store(tmp_out, thi != -1 ? thi * Dh + vi : bhi * Dh + vi); #else store_func.template store(out, thi != -1 ? thi * Dh + vi : bhi * Dh + vi); #endif } #else assert(false); #endif } template __global__ __launch_bounds__(THREADS_PER_BLOCK) void gqa_block_attention_kernel( Block_AttN_params params, LoadFunc load_func, StoreFunc store_func) { #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) const int bi = blockIdx.y; const int act_time_step = params.sequence_lengths[bi]; if (act_time_step == 0) { return; } const int *block_table = params.block_tables + bi * params.max_num_blocks_per_seq; 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_[]; int block_smem_offset = div_up(params.max_num_blocks_per_seq, 4) * 4 * sizeof(int); int q_smem_offset = div_up(Dh_MAX * GQA_SUB_PARTITION_SIZE, 4) * 4 * sizeof(T); T *q_smem = reinterpret_cast(smem_ + block_smem_offset); float *qk_smem = reinterpret_cast(smem_ + block_smem_offset + q_smem_offset); float *logits_smem = reinterpret_cast(smem_ + block_smem_offset + q_smem_offset); /* Note: This shared memory is used in Final Reduce, so we do not need to add offset. Since in Final Reduce Phase, the block tables is not used. */ 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; using QK_Packed_Int8_t = typename Packed_Int8_::Type; const int tid = threadIdx.x; const int kv_hi = blockIdx.x / GQA_NUM_SUB_PARTITIONS; const int gqa_sub_partition_id = blockIdx.x % GQA_NUM_SUB_PARTITIONS; float k_quant_scale; float v_quant_scale; float k_dequant_scale; float v_dequant_scale; if (CACHE_TYPE == CacheType::INT8) { k_quant_scale = static_cast(params.cache_k_quant_scales[kv_hi]); v_quant_scale = static_cast(params.cache_v_quant_scales[kv_hi]); k_dequant_scale = static_cast(params.cache_k_dequant_scales[kv_hi]); v_dequant_scale = static_cast(params.cache_v_dequant_scales[kv_hi]); } const int ti = params.cum_offsets ? bi * params.seq_len - params.cum_offsets[bi] : -1; int *block_table_smem = reinterpret_cast(smem_); for (int local_id = tid; local_id < params.max_num_blocks_per_seq; local_id += blockDim.x) { block_table_smem[local_id] = block_table[local_id]; } __syncthreads(); float qk_max = -FLT_MAX; float qk = 0; const int block_idx = act_time_step / BLOCK_SIZE; const int block_offset = act_time_step % BLOCK_SIZE; const int physical_block_number = block_table_smem[block_idx]; // cache offset of current token const int base_cache_offset = physical_block_number * params.kv_num_head * BLOCK_SIZE * Dh + kv_hi * BLOCK_SIZE * Dh + block_offset * Dh; // qkv [B, S=1, num_head + 2 * (kv_num_head), head_dim] int qkv_base_offset = bi * (params.q_num_head + params.kv_num_head * 2) * Dh; 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_bias_base = nullptr; const T *k_bias_base = nullptr; if (params.add_qkv_bias) { q_bias_base = params.qkv_bias; k_bias_base = params.qkv_bias + params.q_num_head * Dh; } // Load the current timestep's Q and K, then compute q*k, // GQA use one warp calculate one head assert(QK_VECS_PER_WARP < WARP_SIZE); const int warp_id = tid / WARP_SIZE; const int lane_id = tid % WARP_SIZE; if (warp_id < GQA_SUB_PARTITION_SIZE && lane_id < QK_VECS_PER_WARP) { const int hi = kv_hi * GQA_PARTITION_SIZE + gqa_sub_partition_id * GQA_SUB_PARTITION_SIZE + warp_id; const int qk_offset = qkv_base_offset + lane_id * QK_VEC_SIZE; Qk_vec q; zero(q); if (Dh == Dh_MAX || lane_id * QK_VEC_SIZE < Dh) { load_func.template load(q, qk_offset + hi * Dh); } Qk_vec k; zero(k); if (Dh == Dh_MAX || lane_id * QK_VEC_SIZE < Dh) { load_func.template load( k, params.q_num_head * Dh + qk_offset + kv_hi * Dh); } if (params.add_qkv_bias) { const int q_bias_offset = hi * Dh + lane_id * QK_VEC_SIZE; const int k_bias_offset = kv_hi * Dh + lane_id * QK_VEC_SIZE; Qk_vec q_bias; zero(q_bias); Qk_vec k_bias; zero(k_bias); q_bias = (Dh == Dh_MAX || lane_id * QK_VEC_SIZE < Dh) ? *reinterpret_cast(&q_bias_base[q_bias_offset]) : q_bias; k_bias = (Dh == Dh_MAX || lane_id * QK_VEC_SIZE < Dh) ? *reinterpret_cast(&k_bias_base[k_bias_offset]) : k_bias; q = add(q, q_bias); k = add(k, k_bias); } if (params.rotary_emb_dims != 0) { if (!params.neox_rotary_style) { apply_rotary_embedding(q, k, lane_id, Dh, act_time_step, params.inv_compression_ratio, params.rope_theta); } else { int last_dim = Dh / params.rotary_emb_dims; int half_lastdim = last_dim / 2; int rotary_offset = act_time_step * Dh + lane_id * QK_VEC_SIZE; const float *cos_base = params.rotary_emb; const float *sin_base = params.rotary_emb + params.rope_stride; int stride = half_lastdim / QK_VEC_SIZE; int stride_all_lastdim = 2 * stride; int right_id = lane_id / stride_all_lastdim * stride_all_lastdim + (lane_id + stride) % (stride_all_lastdim); int q_right_offset = qkv_base_offset + hi * Dh + right_id * QK_VEC_SIZE; int k_right_offset = qkv_base_offset + params.q_num_head * Dh + kv_hi * Dh + right_id * QK_VEC_SIZE; Qk_vec q_right; zero(q_right); if (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) { load_func.template load(q_right, q_right_offset); } Qk_vec k_right; zero(k_right); if (Dh == Dh_MAX || right_id * QK_VEC_SIZE < Dh) { load_func.template load(k_right, k_right_offset); } Qk_vec_RoPE cos_emb; zero(cos_emb); cos_emb = (Dh == Dh_MAX || lane_id * 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 || lane_id * QK_VEC_SIZE < Dh) ? *reinterpret_cast( &sin_base[rotary_offset]) : sin_emb; float alpha = (lane_id % 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[warp_id * Dh_MAX + lane_id * QK_VEC_SIZE]) = q; if (Dh == Dh_MAX || lane_id * QK_VEC_SIZE < Dh) { if (CACHE_TYPE == CacheType::INT8) { const int offset = base_cache_offset + lane_id * QK_VEC_SIZE; QK_Packed_Int8_t k_tmp = round_tmp( mul(k_quant_scale, k)); *reinterpret_cast(¶ms.k_cache_I[offset]) = k_tmp; } else { const int offset = base_cache_offset + lane_id * QK_VEC_SIZE; *reinterpret_cast(¶ms.k_cache[offset]) = k; } } qk = dot(q, k); if (QK_VECS_PER_WARP <= WARP_SIZE) { #pragma unroll for (int mask = QK_VECS_PER_WARP / 2; mask >= 1; mask /= 2) { #ifdef PADDLE_WITH_HIP qk += __shfl_xor(qk, mask); #else qk += __shfl_xor_sync(shfl_mask(QK_VECS_PER_WARP), qk, mask); #endif } } } if (lane_id == 0) { qk *= params.inv_sqrt_dh; const int hi = kv_hi * GQA_PARTITION_SIZE + gqa_sub_partition_id * GQA_SUB_PARTITION_SIZE + warp_id; const int bhi = bi * params.q_num_head + hi; if (params.attn_mask) { auto mask_bhi = bhi; if (params.mask_broadcast_num_heads) { mask_bhi = bi; } T mask = params.attn_mask[mask_bhi * params.mask_length + act_time_step]; qk += static_cast(mask); } qk_max = qk; qk_smem[act_time_step * GQA_SUB_PARTITION_SIZE + warp_id] = qk; } __syncthreads(); using K_vec = typename K_vec_bttn_::Type; using K_vec_I = typename K_vec_I_bttn_::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; 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]); // } float qk_maxs[GQA_SUB_PARTITION_SIZE]; #pragma unroll for (int i = 0; i < GQA_SUB_PARTITION_SIZE; i++) { // qk_maxs[i] = -FLT_MAX; // initialize qk_maxs!!! qk_maxs[i] = qk_smem[act_time_step * GQA_SUB_PARTITION_SIZE + i]; } // threads in one block can process 'K_PER_ITER' keys constexpr int K_PER_ITER = THREADS_PER_BLOCK / THREADS_PER_KEY; // threads in one warp can process 'K_PER_ITER' keys constexpr int K_PER_WARP = WARP_SIZE / THREADS_PER_KEY; int ti_end = div_up(act_time_step, K_PER_WARP) * K_PER_WARP; K_vec k[K_VECS_PER_THREAD]; K_vec k_vec_zero; zero(k_vec_zero); // ti is the timestep index for (int ti = ko; ti < ti_end; ti += K_PER_ITER) { int physical_block_number = block_table_smem[ti / BLOCK_SIZE]; const int block_offset = ti % BLOCK_SIZE; const int k_offset = physical_block_number * params.kv_num_head * BLOCK_SIZE * Dh + kv_hi * BLOCK_SIZE * Dh + block_offset * Dh + ki; #pragma unroll for (int ii = 0; ii < K_VECS_PER_THREAD; ++ii) { if (ti < act_time_step) { if (CACHE_TYPE == CacheType::INT8) { mul_pointer_v2( &k[ii], k_dequant_scale, reinterpret_cast(params.k_cache_I + k_offset + ii * THREADS_PER_KEY * K_VEC_SIZE)); } else { k[ii] = (Dh == Dh_MAX || ii * THREADS_PER_KEY * K_VEC_SIZE < Dh) ? *reinterpret_cast( params.k_cache + k_offset + ii * THREADS_PER_KEY * K_VEC_SIZE) : k_vec_zero; } } else { k[ii] = k_vec_zero; } } #pragma unroll for (int local_hi = 0; local_hi < GQA_SUB_PARTITION_SIZE; local_hi++) { const int hi = kv_hi * GQA_PARTITION_SIZE + gqa_sub_partition_id * GQA_SUB_PARTITION_SIZE + local_hi; 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[local_hi * Dh_MAX + ki + i * THREADS_PER_KEY * K_VEC_SIZE]); } float qk = Qk_dot::dot(q, k, params.inv_sqrt_dh); if (params.attn_mask) { const int bhi = bi * params.q_num_head + hi; auto mask_bhi = bhi; if (params.mask_broadcast_num_heads) { mask_bhi = bi; } T mask = params.attn_mask[mask_bhi * params.mask_length + ti]; qk += static_cast(mask); } if (ti < act_time_step && tid % THREADS_PER_KEY == 0) { qk_maxs[local_hi] = fmaxf(qk_maxs[local_hi], qk); qk_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi] = qk; } } } #pragma unroll for (int local_hi = 0; local_hi < GQA_SUB_PARTITION_SIZE; local_hi++) { #pragma unroll for (int mask = WARP_SIZE / 2; mask >= THREADS_PER_KEY; mask /= 2) { qk_maxs[local_hi] = fmaxf(qk_maxs[local_hi], #ifdef PADDLE_WITH_HIP __shfl_xor(qk_maxs[local_hi], mask)); #else __shfl_xor_sync( uint32_t(-1), qk_maxs[local_hi], mask)); #endif } if (lane_id == 0) { red_smem[warp_id] = qk_maxs[local_hi]; } __syncthreads(); #ifdef PADDLE_WITH_HIP qk_maxs[local_hi] = -FLT_MAX; qk_maxs[local_hi] = red_smem[lane_id]; #else qk_maxs[local_hi] = lane_id < WARPS_PER_BLOCK ? red_smem[lane_id] : -FLT_MAX; #endif #pragma unroll for (int mask = WARPS_PER_BLOCK / 2; mask >= 1; mask /= 2) { qk_maxs[local_hi] = fmaxf(qk_maxs[local_hi], #ifdef PADDLE_WITH_HIP __shfl_xor(qk_maxs[local_hi], mask)); #else __shfl_xor_sync( uint32_t(-1), qk_maxs[local_hi], mask)); #endif } #ifdef PADDLE_WITH_HIP qk_maxs[local_hi] = __shfl(qk_maxs[local_hi], 0); #else qk_maxs[local_hi] = __shfl_sync(uint32_t(-1), qk_maxs[local_hi], 0); #endif float sum = 0.f; for (int ti = tid; ti <= act_time_step; ti += THREADS_PER_BLOCK) { float logit = __expf(qk_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi] - qk_maxs[local_hi]); sum += logit; qk_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi] = logit; } sum = block_sum(&red_smem[WARPS_PER_BLOCK], sum); float inv_sum = __fdividef(1.f, sum + 1.e-6f); for (int ti = tid; ti <= act_time_step; ti += THREADS_PER_BLOCK) { convert_from_float( logits_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi], qk_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi] * inv_sum); } } // for (int local_hi __syncthreads(); constexpr int V_VEC_SIZE = Dh_MAX / THREADS_PER_VALUE; using V_vec = typename V_vec_::Type; using V_Packed_Int8_t = typename Packed_Int8_::Type; int vo = tid / THREADS_PER_VALUE; int vi = (tid % THREADS_PER_VALUE) * V_VEC_SIZE; #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[GQA_SUB_PARTITION_SIZE]; #pragma unroll for (int i = 0; i < GQA_SUB_PARTITION_SIZE; i++) { zero(out[i]); } constexpr int V_PER_ITER = THREADS_PER_BLOCK / THREADS_PER_VALUE; if (Dh == Dh_MAX || vi < Dh) { for (int ti = vo; ti < act_time_step; ti += V_PER_ITER) { int physical_block_number = block_table_smem[ti / BLOCK_SIZE]; const int block_offset = ti % BLOCK_SIZE; const int v_offset = physical_block_number * params.kv_num_head * BLOCK_SIZE * Dh + kv_hi * BLOCK_SIZE * Dh + block_offset * Dh + vi; V_vec v; if (CACHE_TYPE == CacheType::INT8) { mul_pointer_v2( &v, v_dequant_scale, reinterpret_cast(params.v_cache_I + v_offset)); } else { v = *reinterpret_cast(params.v_cache + v_offset); } #pragma unroll for (int local_hi = 0; local_hi < GQA_SUB_PARTITION_SIZE; local_hi++) { #if defined(MMHA_USE_FP32_ACUM_FOR_LOGITS) float logit = logits_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi]; out[local_hi] = fma(logit, cast_to_float(v), out[local_hi]); #else DataType_ logit = static_cast( logits_smem[ti * GQA_SUB_PARTITION_SIZE + local_hi]); // Update the partial sums. out[local_hi] = fma(logit, v, out[local_hi]); #endif } } } V_vec v_bias; zero(v_bias); if (vo == (act_time_step % V_PER_ITER) && (Dh == Dh_MAX || vi < Dh)) { V_vec v; // Load the current v into the v cache load_func.template load( v, (params.q_num_head + params.kv_num_head) * Dh + qkv_base_offset + kv_hi * Dh + vi); if (params.add_qkv_bias) { v_bias = *reinterpret_cast( ¶ms.qkv_bias[(params.q_num_head + params.kv_num_head) * Dh + kv_hi * Dh + vi]); v = add(v, v_bias); } if (CACHE_TYPE == CacheType::INT8) { V_Packed_Int8_t v_tmp = round_tmp( mul(v_quant_scale, v)); *reinterpret_cast(params.v_cache_I + base_cache_offset + vi) = v_tmp; } else { *reinterpret_cast(params.v_cache + base_cache_offset + vi) = v; } #pragma unroll for (int local_hi = 0; local_hi < GQA_SUB_PARTITION_SIZE; local_hi++) { #if defined(MMHA_USE_FP32_ACUM_FOR_LOGITS) out[local_hi] = fma(logits_smem[act_time_step * GQA_SUB_PARTITION_SIZE + local_hi], cast_to_float(v), out[local_hi]); #else out[local_hi] = fma(logits_smem[act_time_step * GQA_SUB_PARTITION_SIZE + local_hi], v, out[local_hi]); #endif } } __syncthreads(); if (Dh == Dh_MAX || vi < Dh) { #pragma unroll for (int local_hi = 0; local_hi < GQA_SUB_PARTITION_SIZE; local_hi++) { #pragma unroll for (int active_groups = V_PER_ITER; active_groups >= 2; active_groups /= 2) { int midpoint = active_groups / 2; if (vo >= midpoint && vo < active_groups && (Dh == Dh_MAX || vi < Dh)) { #ifdef MMHA_USE_FP32_ACUM_FOR_OUT convert_from_float( *reinterpret_cast(&out_smem[(vo - midpoint) * Dh + vi]), out[local_hi]); #else *reinterpret_cast(&out_smem[(vo - midpoint) * Dh + vi]) = out[local_hi]; #endif } __syncthreads(); if (vo < midpoint && (Dh == Dh_MAX || vi < Dh)) { out[local_hi] = add(*reinterpret_cast(&out_smem[vo * Dh + vi]), out[local_hi]); } __syncthreads(); } } // for (int local_hi = 0 } if (vo == 0 && (Dh == Dh_MAX || vi < Dh)) { #pragma unroll for (int local_hi = 0; local_hi < GQA_SUB_PARTITION_SIZE; local_hi++) { const int hi = kv_hi * GQA_PARTITION_SIZE + gqa_sub_partition_id * GQA_SUB_PARTITION_SIZE + local_hi; const int ti = params.cum_offsets ? bi * params.seq_len - params.cum_offsets[bi] : -1; const int thi = params.cum_offsets ? ti * params.q_num_head + hi : -1; const int bhi = bi * params.q_num_head + hi; #ifdef MMHA_USE_FP32_ACUM_FOR_OUT V_vec tmp_out; convert_from_float(tmp_out, out[local_hi]); store_func.template store( tmp_out, thi != -1 ? thi * Dh + vi : bhi * Dh + vi); #else store_func.template store( out[local_hi], thi != -1 ? thi * Dh + vi : bhi * Dh + vi); #endif } } #else assert(false); #endif } template inline size_t smem_size_in_bytes(const Block_AttN_params ¶ms, int dim_head, int threads_per_value, int threads_per_block) { size_t qk_sz = div_up(params.timestep + 1, 4) * 4 * sizeof(int); size_t block_table_sz = div_up(params.max_num_blocks_per_seq, 4) * 4 * sizeof(int); size_t cache_scale_sz = 0; size_t logits_table_sz = qk_sz + block_table_sz + cache_scale_sz; int rows_per_red = threads_per_block / threads_per_value; // Note: Because half threads in ThreadBlock use out_smem to do reduce. size_t red_sz = rows_per_red * dim_head * sizeof(T) / 2; return max(logits_table_sz, red_sz); } template inline size_t gqa_smem_size_in_bytes(const Block_AttN_params ¶ms, int dim_head, int threads_per_value, int threads_per_block) { size_t qk_sz = div_up(dim_head * GQA_SUB_PARTITION_SIZE + params.timestep * GQA_SUB_PARTITION_SIZE + 1, 4) * 4 * sizeof(int); size_t block_table_sz = div_up(params.max_num_blocks_per_seq, 4) * 4 * sizeof(int); size_t logits_table_sz = qk_sz + block_table_sz; int rows_per_red = threads_per_block / threads_per_value; size_t red_sz = rows_per_red * dim_head * sizeof(T) / 2; return max(logits_table_sz, red_sz); } #ifdef PADDLE_WITH_HIP #define BLHAG_LAUNCH_KERNEL(T, \ SUM_T, \ Dh, \ Dh_MAX, \ THDS_PER_KEY, \ THDS_PER_VALUE, \ THDS_PER_BLOCK, \ BLOCK_SIZE, \ CACHE_TYPE, \ stream, \ load_func, \ store_func) \ size_t smem_sz = \ smem_size_in_bytes(params, Dh, THDS_PER_VALUE, THDS_PER_BLOCK); \ constexpr auto kernel_fn = block_attention_kernel; \ if (smem_sz > 0xc000) { \ hipError_t result = \ hipFuncSetAttribute((const void *)kernel_fn, \ hipFuncAttributeMaxDynamicSharedMemorySize, \ smem_sz); \ if (result != hipSuccess) { \ result = hipGetLastError(); \ PADDLE_THROW(::common::errors::Unavailable( \ " hipFuncSetAttribute() returned error %s", \ hipGetErrorString(result))); \ } \ } \ dim3 grid(params.q_num_head, params.batch_size); \ kernel_fn<<>>( \ params, load_func, store_func); // For GQA specific optimization (use xqa) #define BLHA_LAUNCH_GQA_KERNEL(T, \ Dh, \ Dh_MAX, \ THDS_PER_KEY, \ THDS_PER_VALUE, \ THDS_PER_BLOCK, \ BLOCK_SIZE, \ CACHE_TYPE, \ GQA_PARTITION_SIZE, \ GQA_SUB_PARTITION_SIZE, \ stream, \ load_func, \ store_func) \ size_t smem_sz = gqa_smem_size_in_bytes( \ params, Dh, THDS_PER_VALUE, THDS_PER_BLOCK); \ constexpr int GQA_NUM_SUB_PARTITIONS = \ GQA_PARTITION_SIZE / GQA_SUB_PARTITION_SIZE; \ constexpr auto kernel_fn = \ gqa_block_attention_kernel; \ if (smem_sz > 0xc000) { \ hipError_t result = \ hipFuncSetAttribute((const void *)kernel_fn, \ hipFuncAttributeMaxDynamicSharedMemorySize, \ smem_sz); \ if (result != hipSuccess) { \ result = hipGetLastError(); \ PADDLE_THROW(::common::errors::Unavailable( \ " hipFuncSetAttribute() returned error %s", \ hipGetErrorString(result))); \ } \ } \ dim3 grid(params.kv_num_head *GQA_NUM_SUB_PARTITIONS, params.batch_size); \ kernel_fn<<>>( \ params, load_func, store_func); #else #define BLHAG_LAUNCH_KERNEL(T, \ SUM_T, \ Dh, \ Dh_MAX, \ THDS_PER_KEY, \ THDS_PER_VALUE, \ THDS_PER_BLOCK, \ BLOCK_SIZE, \ CACHE_TYPE, \ stream, \ load_func, \ store_func) \ size_t smem_sz = \ smem_size_in_bytes(params, Dh, THDS_PER_VALUE, THDS_PER_BLOCK); \ constexpr auto kernel_fn = block_attention_kernel; \ if (smem_sz > 0xc000) { \ cudaError_t result = cudaFuncSetAttribute( \ kernel_fn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_sz); \ if (result != cudaSuccess) { \ result = cudaGetLastError(); \ PADDLE_THROW(::common::errors::Unavailable( \ " cudaFuncSetAttribute() returned error %s", \ cudaGetErrorString(result))); \ } \ } \ dim3 grid(params.q_num_head, params.batch_size); \ kernel_fn<<>>( \ params, load_func, store_func); // For GQA specific optimization (use xqa) #define BLHA_LAUNCH_GQA_KERNEL(T, \ Dh, \ Dh_MAX, \ THDS_PER_KEY, \ THDS_PER_VALUE, \ THDS_PER_BLOCK, \ BLOCK_SIZE, \ CACHE_TYPE, \ GQA_PARTITION_SIZE, \ GQA_SUB_PARTITION_SIZE, \ stream, \ load_func, \ store_func) \ size_t smem_sz = gqa_smem_size_in_bytes( \ params, Dh, THDS_PER_VALUE, THDS_PER_BLOCK); \ constexpr int GQA_NUM_SUB_PARTITIONS = \ GQA_PARTITION_SIZE / GQA_SUB_PARTITION_SIZE; \ constexpr auto kernel_fn = \ gqa_block_attention_kernel; \ if (smem_sz > 0xc000) { \ cudaError_t result = cudaFuncSetAttribute( \ kernel_fn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_sz); \ if (result != cudaSuccess) { \ result = cudaGetLastError(); \ PADDLE_THROW(::common::errors::Unavailable( \ " cudaFuncSetAttribute() returned error %s", \ cudaGetErrorString(result))); \ } \ } \ dim3 grid(params.kv_num_head *GQA_NUM_SUB_PARTITIONS, params.batch_size); \ kernel_fn<<>>( \ params, load_func, store_func); #endif template void dispatch_blha_impl_kernel(const Block_AttN_params ¶ms, const GPU(Stream_t) & stream, LoadFunc load_func, StoreFunc store_func) { VLOG(1) << "group wise"; BLHAG_LAUNCH_KERNEL(T, SUM_T, Dh, Dh_MAX, THREADS_PER_KEY, THREADS_PER_VALUE, THREADS_PER_BLOCK, BlockSize, CACHE_TYPE, stream, load_func, store_func) } template void dispatch_blha_gqa_kernel(const Block_AttN_params ¶ms, const GPU(Stream_t) & stream, LoadFunc load_func, StoreFunc store_func) { if (params.gqa_num_per_partitions == 1 || !FLAGS_use_xqa_optim) { auto dispatch_blha_kernel = [&](auto kernel_type, auto qk_sum_type) { using Kernel_T = decltype(kernel_type); using SUM_T = decltype(qk_sum_type); dispatch_blha_impl_kernel( params, stream, load_func, store_func); }; if (FLAGS_blha_use_fp32_qk_sum) { if constexpr (std::is_same_v) { dispatch_blha_kernel(float16{}, float{}); } else { dispatch_blha_kernel(T{}, T{}); } } else { dispatch_blha_kernel(T{}, T{}); } } else if (params.gqa_num_per_partitions == 2) { constexpr int THDS_PER_BLOCK = 1024; BLHA_LAUNCH_GQA_KERNEL(T, Dh, Dh_MAX, THREADS_PER_KEY, THREADS_PER_VALUE, THDS_PER_BLOCK, BlockSize, CACHE_TYPE, 2, 2, stream, load_func, store_func) } else if (params.gqa_num_per_partitions == 4) { constexpr int THDS_PER_BLOCK = 1024; BLHA_LAUNCH_GQA_KERNEL(T, Dh, Dh_MAX, THREADS_PER_KEY, THREADS_PER_VALUE, THDS_PER_BLOCK, BlockSize, CACHE_TYPE, 4, 4, stream, load_func, store_func) } else if (params.gqa_num_per_partitions == 6) { constexpr int THDS_PER_BLOCK = 1024; BLHA_LAUNCH_GQA_KERNEL(T, Dh, Dh_MAX, THREADS_PER_KEY, THREADS_PER_VALUE, THDS_PER_BLOCK, BlockSize, CACHE_TYPE, 6, 2, stream, load_func, store_func) } else if (params.gqa_num_per_partitions == 7) { constexpr int THDS_PER_BLOCK = 1024; BLHA_LAUNCH_GQA_KERNEL(T, Dh, Dh_MAX, THREADS_PER_KEY, THREADS_PER_VALUE, THDS_PER_BLOCK, BlockSize, CACHE_TYPE, 7, 1, stream, load_func, store_func) } else if (params.gqa_num_per_partitions == 8) { constexpr int THDS_PER_BLOCK = 1024; BLHA_LAUNCH_GQA_KERNEL(T, Dh, Dh_MAX, THREADS_PER_KEY, THREADS_PER_VALUE, THDS_PER_BLOCK, BlockSize, CACHE_TYPE, 8, 4, stream, load_func, store_func) } else { PADDLE_THROW(common::errors::Unimplemented( "gqa_num_per_partitions = %d is unsupported!", params.gqa_num_per_partitions)); } } template void dispatch_blha_impl(const Block_AttN_params ¶ms, const GPU(Stream_t) & stream, LoadFunc load_func, StoreFunc store_func, const int use_cachekv_int8) { constexpr int THREADS_PER_VALUE = Dh_MAX * sizeof(T) / 16; if (use_cachekv_int8 > 0) { VLOG(1) << "cache_int8"; dispatch_blha_gqa_kernel( params, stream, load_func, store_func); } else { VLOG(1) << "normal cache"; dispatch_blha_gqa_kernel( params, stream, load_func, store_func); } } template void dispatch_blha_impl_key_and_thread(const Block_AttN_params ¶ms, const GPU(Stream_t) & stream, LoadFunc load_func, StoreFunc store_func, const int use_cachekv_int8) { dispatch_blha_impl( params, stream, load_func, store_func, use_cachekv_int8); } template void dispatch_blha_impl_blocksize(const Block_AttN_params ¶ms, const GPU(Stream_t) & stream, LoadFunc load_func, StoreFunc store_func, const int use_cachekv_int8) { switch (params.block_size) { case 1: dispatch_blha_impl_key_and_thread( params, stream, load_func, store_func, use_cachekv_int8); break; case 32: dispatch_blha_impl_key_and_thread( params, stream, load_func, store_func, use_cachekv_int8); break; case 64: dispatch_blha_impl_key_and_thread( params, stream, load_func, store_func, use_cachekv_int8); break; case 128: dispatch_blha_impl_key_and_thread( params, stream, load_func, store_func, use_cachekv_int8); break; default: PADDLE_THROW(common::errors::Unimplemented( "block_size = %d is unsupported!", params.block_size)); } } template void dispatch_blha_impl_headsize(const GPUContext &dev_ctx, const Block_AttN_params ¶ms, int dim_head, LoadFunc load_func, StoreFunc store_func, const int use_cachekv_int8) { switch (dim_head) { case 32: dispatch_blha_impl_blocksize( params, dev_ctx.stream(), load_func, store_func, use_cachekv_int8); break; case 64: dispatch_blha_impl_blocksize( params, dev_ctx.stream(), load_func, store_func, use_cachekv_int8); break; case 96: dispatch_blha_impl_blocksize( params, dev_ctx.stream(), load_func, store_func, use_cachekv_int8); break; case 128: dispatch_blha_impl_blocksize( params, dev_ctx.stream(), load_func, store_func, use_cachekv_int8); break; default: PADDLE_THROW(common::errors::Unimplemented( "Dim_head = %d is unsupported!", dim_head)); } } template void DispatchBLHA(const GPUContext &dev_ctx, const DenseTensor &qkv_tensor, const Block_AttN_params ¶ms, int q_num_head, int kv_num_head, int dim_head, int use_cachekv_int8, DenseTensor *out_tensor) { MMHALoad load_func(qkv_tensor.data()); MMHAStore store_func(out_tensor->data()); dispatch_blha_impl_headsize( dev_ctx, params, dim_head, load_func, store_func, use_cachekv_int8); } template void blha(const GPUContext &dev_ctx, const DenseTensor &qkv_tensor, const DenseTensor *qkv_bias_tensor, const DenseTensor *block_tables, const DenseTensor *src_mask_tensor, const DenseTensor *cum_offsets_tensor, const DenseTensor *sequence_lengths_tensor, const DenseTensor *rotary_tensor, DenseTensor *k_cache, DenseTensor *v_cache, DenseTensor *out_tensor, const int batch_size, const int max_num_blocks_per_seq, const int block_size, const int seq_len, const int pre_cache_length, const int q_num_head, const int kv_num_head, const int dim_head, const int timestep, const int rotary_emb_dims, float inv_sqrt_dh, const float rope_theta, const bool add_qkv_bias = true, const bool neox_rotary_style = false, const int quant_round_type = 1, const float quant_max_bound = 127.0f, const float quant_min_bound = -127.0f, const DenseTensor *cache_k_quant_scales = nullptr, const DenseTensor *cache_v_quant_scales = nullptr, const DenseTensor *cache_k_dequant_scales = nullptr, const DenseTensor *cache_v_dequant_scales = nullptr, const DenseTensor *dequant_qkv_scales = nullptr, const DenseTensor *shift = nullptr, const DenseTensor *smooth = nullptr, const float quant_fmha_out_scale = -1, int use_cachekv_int8 = 0) { Block_AttN_params params; if (cache_k_quant_scales) { VLOG(1) << "blha quant cachekv"; params.k_cache_I = k_cache->data(); params.v_cache_I = v_cache->data(); params.cache_k_quant_scales = cache_k_quant_scales->data(); params.cache_v_quant_scales = cache_v_quant_scales->data(); params.cache_k_dequant_scales = cache_k_dequant_scales->data(); params.cache_v_dequant_scales = cache_v_dequant_scales->data(); } else { VLOG(1) << "blha not quant cachekv"; params.k_cache = k_cache->data(); params.v_cache = v_cache->data(); } params.max_num_blocks_per_seq = max_num_blocks_per_seq; params.neox_rotary_style = neox_rotary_style; params.attn_mask = nullptr; bool mask_broadcast_num_heads = false; if (src_mask_tensor) { if (src_mask_tensor->dims()[1] == 1) { // all head share a mask. mask_broadcast_num_heads = true; } else if (src_mask_tensor->dims()[1] == q_num_head) { mask_broadcast_num_heads = false; } else { PADDLE_THROW(errors::InvalidArgument( "Unknown dimension for attn_mask, the q_num_head(2nd) " "dimension is invalid, it should be 1 or q_num_head(%d), " "but got %d", q_num_head, src_mask_tensor->dims()[1])); } params.attn_mask = src_mask_tensor->data(); params.mask_broadcast_num_heads = mask_broadcast_num_heads; params.mask_length = src_mask_tensor->dims()[3]; } else { params.attn_mask = nullptr; } params.block_tables = block_tables->data(); if (sequence_lengths_tensor) { params.sequence_lengths = sequence_lengths_tensor->data(); } if (cum_offsets_tensor) { params.cum_offsets = cum_offsets_tensor->data(); } else { params.cum_offsets = nullptr; } params.seq_len = seq_len; params.pre_cache_length = pre_cache_length; if (rotary_tensor) { params.rotary_emb = rotary_tensor->data(); params.rope_stride = rotary_tensor->dims()[2] * rotary_tensor->dims()[4]; } else { params.rotary_emb = nullptr; } params.add_qkv_bias = add_qkv_bias; if (add_qkv_bias) { params.qkv_bias = qkv_bias_tensor->data(); } params.batch_size = batch_size; params.block_size = block_size; params.q_num_head = q_num_head; params.kv_num_head = kv_num_head; params.gqa_num_per_partitions = q_num_head / kv_num_head; params.timestep = timestep + pre_cache_length; params.inv_sqrt_dh = inv_sqrt_dh; params.rotary_emb_dims = rotary_emb_dims; params.rope_theta = rope_theta; VLOG(3) << "batch_size: " << batch_size << " q_num_head: " << q_num_head << " kv_num_head: " << kv_num_head << " block_size: " << block_size << " timestep: " << timestep; DispatchBLHA(dev_ctx, qkv_tensor, params, q_num_head, kv_num_head, dim_head, use_cachekv_int8, out_tensor); } inline GPU(Error_t) GetNumBlocks(int64_t n, int *num_blocks) { constexpr int kBlockSize = 128; constexpr int kNumWaves = 16; const int device_id = backends::gpu::GetCurrentDeviceId(); const int sm_count = backends::gpu::GetGPUMultiProcessors(device_id); const int max_thread_per_multiprocessor = backends::gpu::GetGPUMaxThreadsPerMultiProcessor(device_id); *num_blocks = std::max(1, std::min((n + kBlockSize - 1) / kBlockSize, sm_count * max_thread_per_multiprocessor / kBlockSize * kNumWaves)); return GPU(Success); } template inline GPU(Error_t) GetNumBlocks(Func func, int64_t block_size, size_t dynamic_smem_size, int64_t max_blocks, int64_t waves, int *num_blocks) { int dev; { GPU(Error_t) err = GPU(GetDevice)(&dev); if (err != GPU(Success)) { return err; } } int sm_count; { GPU(Error_t) err = GPU(DeviceGetAttribute)(&sm_count, GPUMultiProcessorCount, dev); if (err != GPU(Success)) { return err; } } int max_active_blocks; { GPU(Error_t) err = GPU(OccupancyMaxActiveBlocksPerMultiprocessor)( &max_active_blocks, func, block_size, dynamic_smem_size); } *num_blocks = std::max( 1, std::min(max_blocks, sm_count * max_active_blocks * waves)); return GPU(Success); } template __global__ void cache_int8_kernel( const T *__restrict__ qkv, // [num_tokens, num_heads + 2 * kv_num_heads, // head_size] uint8_t *__restrict__ key_cache, // [num_blocks, kv_num_heads, // block_size, head_size] uint8_t *__restrict__ value_cache, // [num_blocks, kv_num_heads, // block_size, head_size] const int *__restrict__ block_tables, // [bsz, max_blocks_per_seq] const int *__restrict__ padding_offsets, // [num_tokens] const int *__restrict__ seq_lens, // [bsz] const float *cache_k_scales, const float *cache_v_scales, const int max_seq_len, const int max_blocks_per_seq, const int q_num_heads, const int kv_num_heads, const int head_size, const int block_size, const int pre_cache_length, const int elem_cnt, const int round_type, const float max_bound, const float min_bound) { using LoadT = AlignedVector; using LoadKVT = AlignedVector; LoadT src_vec; LoadKVT cache_vec; uint32_t global_thread_idx = blockDim.x * blockIdx.x + threadIdx.x; const uint32_t hidden_size = kv_num_heads * head_size; const uint32_t offset = 2 * hidden_size; for (uint32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const uint32_t token_idx = linear_index / offset; const uint32_t bias = linear_index % offset; const uint32_t qkv_id = bias / hidden_size; // skip q const uint32_t qkv_bias = bias % hidden_size; const uint32_t hi = qkv_bias / head_size; const uint32_t h_bias = qkv_bias % head_size; const uint32_t ori_token_idx = token_idx + padding_offsets[token_idx]; const uint32_t ori_bi = ori_token_idx / max_seq_len; if (seq_lens[ori_bi] == 0) continue; const uint32_t ori_seq_id = ori_token_idx % max_seq_len + pre_cache_length; const int32_t *block_table_now = block_tables + ori_bi * max_blocks_per_seq; const uint32_t block_idx = block_table_now[ori_seq_id / block_size]; const uint32_t block_offset = ori_seq_id % block_size; const uint32_t tgt_idx = block_idx * kv_num_heads * block_size * head_size + hi * block_size * head_size + block_offset * head_size + h_bias; const uint32_t ori_idx = token_idx * (q_num_heads + 2 * kv_num_heads) * head_size + q_num_heads * head_size + qkv_id * hidden_size + hi * head_size + h_bias; Load(&qkv[ori_idx], &src_vec); const uint32_t cache_idx = hi; #ifdef PADDLE_WITH_HIP T scale; if constexpr (kernel_dtype_is_same::value) { scale = qkv_id == 0 ? __float2half(cache_k_scales[cache_idx]) : __float2half(cache_v_scales[cache_idx]); } else { scale = qkv_id == 0 ? static_cast(cache_k_scales[cache_idx]) : static_cast(cache_v_scales[cache_idx]); } #else const T scale = qkv_id == 0 ? cache_k_scales[cache_idx] : cache_v_scales[cache_idx]; #endif #pragma unroll for (uint32_t i = 0; i < VecSize; i++) { #ifdef PADDLE_WITH_HIP float quant_value; if constexpr (kernel_dtype_is_same::value) { quant_value = __half2float(scale * src_vec[i]); } else { quant_value = static_cast(scale * src_vec[i]); } #else float quant_value = static_cast(scale * src_vec[i]); #endif if (round_type == 0) { quant_value = static_cast(roundWithTiesToEven(quant_value)); } else { quant_value = static_cast(round(quant_value)); } quant_value = quant_value > max_bound ? max_bound : quant_value; quant_value = quant_value < min_bound ? min_bound : quant_value; cache_vec[i] = static_cast(quant_value + 128.0f); } if (qkv_id == 0) { Store(cache_vec, &key_cache[tgt_idx]); } else { Store(cache_vec, &value_cache[tgt_idx]); } } } template __global__ void cache_kernel( const T *__restrict__ qkv, // [num_tokens, num_heads + 2 * kv_num_heads, // head_size] T *__restrict__ key_cache, // [num_blocks, kv_num_heads, block_size, // head_size] T *__restrict__ value_cache, // [num_blocks, kv_num_heads, block_size, // head_size] const int *__restrict__ block_tables, // [bsz, max_blocks_per_seq] const int *__restrict__ padding_offsets, // [num_tokens] const int *__restrict__ seq_lens, // [bsz] const int max_seq_len, const int max_blocks_per_seq, const int q_num_heads, const int kv_num_heads, const int head_size, const int block_size, const int pre_cache_length, const int elem_cnt) { using LoadT = AlignedVector; LoadT src_vec; uint32_t global_thread_idx = blockDim.x * blockIdx.x + threadIdx.x; const uint32_t hidden_size = kv_num_heads * head_size; const uint32_t offset = 2 * hidden_size; for (uint32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const uint32_t token_idx = linear_index / offset; const uint32_t bias = linear_index % offset; const uint32_t qkv_id = bias / hidden_size; // skip q const uint32_t qkv_bias = bias % hidden_size; const uint32_t hi = qkv_bias / head_size; const uint32_t h_bias = qkv_bias % head_size; const uint32_t ori_token_idx = token_idx + padding_offsets[token_idx]; const uint32_t ori_bi = ori_token_idx / max_seq_len; if (seq_lens[ori_bi] == 0) continue; const uint32_t ori_seq_id = ori_token_idx % max_seq_len + pre_cache_length; const int32_t *block_table_now = block_tables + ori_bi * max_blocks_per_seq; const uint32_t block_idx = block_table_now[ori_seq_id / block_size]; const uint32_t block_offset = ori_seq_id % block_size; const uint32_t tgt_idx = block_idx * kv_num_heads * block_size * head_size + hi * block_size * head_size + block_offset * head_size + h_bias; const uint32_t ori_idx = token_idx * (q_num_heads + 2 * kv_num_heads) * head_size + q_num_heads * head_size + qkv_id * hidden_size + hi * head_size + h_bias; Load(&qkv[ori_idx], &src_vec); if (qkv_id == 0) { Store(src_vec, &key_cache[tgt_idx]); } else { Store(src_vec, &value_cache[tgt_idx]); } } } template __global__ void write_pre_cache_int8_to_cache( uint8_t *__restrict__ key_cache, // [num_blocks, num_heads, block_size, // head_size] uint8_t *__restrict__ value_cache, const T *__restrict__ pre_key_cache, // [bsz, pre_cache_len, num_head, // head_dim] const T *__restrict__ pre_value_cache, const int *__restrict__ block_tables, // [bsz, max_blocks_per_seq] const int *__restrict__ seq_lens, const float *cache_k_scales, const float *cache_v_scales, const int max_seq_len, const int max_blocks_per_seq, const int num_heads, const int head_size, const int block_size, const int pre_cache_length, const int elem_cnt, const int round_type, const float max_bound, const float min_bound) { using LoadT = AlignedVector; using LoadKVT = AlignedVector; LoadT src_vec; LoadKVT cache_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int hidden_size = pre_cache_length * head_size; const int cache_hidden_size = num_heads * hidden_size; const int offset = 2 * cache_hidden_size; for (int32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int batch_id = linear_index / offset; if (seq_lens[batch_id] == 0) continue; const int *block_table_now = block_tables + batch_id * max_blocks_per_seq; const int32_t cache_seq_id = (linear_index % hidden_size) / head_size; const int32_t head_id = (linear_index % cache_hidden_size) / hidden_size; const int32_t size_id = linear_index % head_size; const int32_t kv_id = (linear_index % offset) / cache_hidden_size; const int32_t read_id = batch_id * cache_hidden_size + head_id * hidden_size + cache_seq_id * head_size + size_id; if (kv_id == 0) { Load(&pre_key_cache[read_id], &src_vec); } else { Load(&pre_value_cache[read_id], &src_vec); } const int block_idx = block_table_now[cache_seq_id / block_size]; const int block_offset = cache_seq_id % block_size; const int tgt_idx = block_idx * num_heads * block_size * head_size + head_id * block_size * head_size + block_offset * head_size + size_id; const float scale = kv_id == 0 ? cache_k_scales[head_id] : cache_v_scales[head_id]; #pragma unroll for (int i = 0; i < VecSize; i++) { #ifdef PADDLE_WITH_HIP float quant_value; if constexpr (kernel_dtype_is_same::value) { quant_value = scale * __half2float(src_vec[i]); } else { quant_value = scale * static_cast(src_vec[i]); } #else float quant_value = scale * static_cast(src_vec[i]); #endif if (round_type == 0) { quant_value = static_cast(roundWithTiesToEven(quant_value)); } else { quant_value = static_cast(round(quant_value)); } quant_value = quant_value > max_bound ? max_bound : quant_value; quant_value = quant_value < min_bound ? min_bound : quant_value; cache_vec[i] = static_cast(quant_value + 128.0f); } if (kv_id == 0) { Store(cache_vec, &key_cache[tgt_idx]); } else { Store(cache_vec, &value_cache[tgt_idx]); } } } template __global__ void write_pre_cache_to_cache( T *__restrict__ key_cache, // [num_blocks, num_heads, block_size, // head_size] T *__restrict__ value_cache, const T *__restrict__ pre_key_cache, // [bsz, num_heads, pre_cache_len, // head_dim] const T *__restrict__ pre_value_cache, const int *__restrict__ block_tables, // [bsz, max_blocks_per_seq] const int *__restrict__ seq_lens, const int max_seq_len, const int max_blocks_per_seq, const int num_heads, const int head_size, const int block_size, const int pre_cache_length, const int elem_cnt) { using LoadT = AlignedVector; LoadT src_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int hidden_size = pre_cache_length * head_size; const int cache_hidden_size = num_heads * hidden_size; const int offset = 2 * cache_hidden_size; for (int32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int batch_id = linear_index / offset; if (seq_lens[batch_id] == 0) continue; const int *block_table_now = block_tables + batch_id * max_blocks_per_seq; const int32_t cache_seq_id = (linear_index % hidden_size) / head_size; const int32_t head_id = (linear_index % cache_hidden_size) / hidden_size; const int32_t size_id = linear_index % head_size; const int32_t kv_id = (linear_index % offset) / cache_hidden_size; const int32_t read_id = batch_id * cache_hidden_size + head_id * hidden_size + cache_seq_id * head_size + size_id; if (kv_id == 0) { Load(&pre_key_cache[read_id], &src_vec); } else { Load(&pre_value_cache[read_id], &src_vec); } const int block_idx = block_table_now[cache_seq_id / block_size]; const int block_offset = cache_seq_id % block_size; const int tgt_idx = block_idx * num_heads * block_size * head_size + head_id * block_size * head_size + block_offset * head_size + size_id; if (kv_id == 0) { Store(src_vec, &key_cache[tgt_idx]); } else { Store(src_vec, &value_cache[tgt_idx]); } } } template void CacheKernel(const GPUContext &dev_ctx, const DenseTensor &qkv, // [token_num, (2 * kv_num_head + // q_num_head), head_dim] const DenseTensor &block_tables, const DenseTensor &padding_offsets, const DenseTensor &seq_lens, const optional &pre_key_cache, const optional &pre_value_cache, const optional &cache_k_scales, const optional &cache_v_scales, const int batch_size, const int num_tokens, const int q_num_heads, const int kv_num_heads, const int head_size, const int max_seq_len, const int pre_cache_length, DenseTensor *key_cache_out, DenseTensor *value_cache_out, const int round_type = 0, const float max_bound = 127.0, const float min_bound = -127.0) { typedef PDDataTypeTraits traits_; typedef typename traits_::DataType DataType_; // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t max_blocks_per_seq = block_tables.dims()[1]; // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t block_size = key_cache_out->dims()[2]; // stage 1: write qkv to cache [pre_cache_length:] int elem_nums = num_tokens * 2 * kv_num_heads * head_size; // just k and v constexpr int PackSize = 16 / sizeof(T); int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (cache_k_scales) { VLOG(1) << "cache kv quant"; cache_int8_kernel <<>>( reinterpret_cast(const_cast(qkv.data())), key_cache_out->data(), value_cache_out->data(), block_tables.data(), padding_offsets.data(), seq_lens.data(), cache_k_scales.get().data(), cache_v_scales.get().data(), max_seq_len, max_blocks_per_seq, q_num_heads, kv_num_heads, head_size, block_size, pre_cache_length, elem_nums, round_type, max_bound, min_bound); } else { VLOG(1) << "cache kv not quant"; cache_kernel <<>>( reinterpret_cast(const_cast(qkv.data())), reinterpret_cast(key_cache_out->data()), reinterpret_cast(value_cache_out->data()), block_tables.data(), padding_offsets.data(), seq_lens.data(), max_seq_len, max_blocks_per_seq, q_num_heads, kv_num_heads, head_size, block_size, pre_cache_length, elem_nums); } if (pre_key_cache) { // NOTE: not support gqa // stage 2: write pre_cache to cache [:pre_cache_length] elem_nums = batch_size * kv_num_heads * pre_cache_length * head_size * 2; pack_num = elem_nums / PackSize; GetNumBlocks(pack_num, &grid_size); if (cache_k_scales) { write_pre_cache_int8_to_cache <<>>( key_cache_out->data(), value_cache_out->data(), reinterpret_cast( pre_key_cache.get().data()), reinterpret_cast( pre_value_cache.get().data()), block_tables.data(), seq_lens.data(), cache_k_scales->data(), cache_v_scales->data(), max_seq_len, max_blocks_per_seq, kv_num_heads, head_size, block_size, pre_cache_length, elem_nums, round_type, max_bound, min_bound); } else { write_pre_cache_to_cache <<>>( reinterpret_cast(key_cache_out->data()), reinterpret_cast(value_cache_out->data()), reinterpret_cast( pre_key_cache.get().data()), reinterpret_cast( pre_value_cache.get().data()), block_tables.data(), seq_lens.data(), max_seq_len, max_blocks_per_seq, kv_num_heads, head_size, block_size, pre_cache_length, elem_nums); } } } template __global__ void quant_write_cache_int8_kernel( const T *__restrict__ qkv, // [num_tokens, 2 * kv_num_heads + q_num_heads, // head_size] uint8_t *__restrict__ key_cache, // [num_blocks, kv_num_heads, block_size, // head_size] uint8_t *__restrict__ value_cache, // [num_blocks, kv_num_heads, // block_size, head_size] const int *__restrict__ block_tables, // [bsz, max_blocks_per_seq] const int *__restrict__ padding_offsets, // [num_tokens] const int *__restrict__ seq_lens, // [bsz] const int max_seq_len, const int pre_cache_length, const int max_blocks_per_seq, const int num_tokens, const int q_num_heads, const int kv_num_heads, const int head_size, const int block_size, float *k_quant_scales, float *v_quant_scales, float *k_dequant_scales, float *v_dequant_scales) { const int hi = blockIdx.x; const int b_id = blockIdx.y; if (seq_lens[b_id] <= 0) return; const int qkv_id = blockIdx.z; const int head_group_size = q_num_heads / kv_num_heads; using InVec = AlignedVector; using OutVec = AlignedVector; InVec in_vec; OutVec out_vec; InVec abs_max_vec; #pragma unroll for (int i = 0; i < VecSize; ++i) { #ifdef PADDLE_WITH_HIP if constexpr (kernel_dtype_is_same::value) { abs_max_vec[i] = __float2half(0.0f); } else { abs_max_vec[i] = static_cast(0.0f); } #else abs_max_vec[i] = 0.0f; #endif } uint8_t *dst_ptr; float *quant_scales; float *dequant_scales; if (qkv_id == 0) { dst_ptr = key_cache; quant_scales = k_quant_scales; dequant_scales = k_dequant_scales; } else { dst_ptr = value_cache; quant_scales = v_quant_scales; dequant_scales = v_dequant_scales; } T local_abs_max; for (int idx = threadIdx.x * VecSize; idx < num_tokens * head_size; idx += blockDim.x * VecSize) { int token_idx = idx / head_size; int h_offset = idx % head_size; int linear_idx = token_idx * (2 * kv_num_heads + q_num_heads) * head_size + (qkv_id + head_group_size) * kv_num_heads * head_size + hi * head_size + h_offset; Load(qkv + linear_idx, &in_vec); #pragma unroll for (int i = 0; i < VecSize; ++i) { abs_max_vec[i] = MaxFunc()(abs_max_vec[i], AbsFunc()(in_vec[i])); } } local_abs_max = LocalReduceMax(abs_max_vec); T abs_max_val = BlockReduceAbsMax(local_abs_max, 0xffffffff); __shared__ float quant_scale; if (threadIdx.x == 0) { #ifdef PADDLE_WITH_HIP if constexpr (kernel_dtype_is_same::value) { quant_scale = 127.0f / __half2float(abs_max_val); } else { quant_scale = 127.0f / static_cast(abs_max_val); } #else quant_scale = 127.0f / static_cast(abs_max_val); #endif } __syncthreads(); for (int idx = threadIdx.x * VecSize; idx < num_tokens * head_size; idx += blockDim.x * VecSize) { int token_idx = idx / head_size; int h_offset = idx % head_size; int linear_idx = token_idx * (2 * kv_num_heads + q_num_heads) * head_size + (qkv_id + head_group_size) * kv_num_heads * head_size + hi * head_size + h_offset; Load(qkv + linear_idx, &in_vec); #pragma unroll for (int i = 0; i < VecSize; ++i) { out_vec[i] = QuantFunc()(in_vec[i], quant_scale); } const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / max_seq_len; if (ori_bi != b_id) continue; const int ori_seq_id = ori_token_idx % max_seq_len + pre_cache_length; const int *block_table_now = block_tables + ori_bi * max_blocks_per_seq; const int block_idx = block_table_now[ori_seq_id / block_size]; const int block_offset = ori_seq_id % block_size; // [max_block_num, num_head, block_size, head_dim/x, x] Store(out_vec, dst_ptr + block_idx * kv_num_heads * block_size * head_size + hi * block_size * head_size + block_offset * head_size + h_offset); } if (threadIdx.x == 0) { quant_scales[b_id * kv_num_heads + hi] = quant_scale; dequant_scales[b_id * kv_num_heads + hi] = 1.0f / quant_scale; } } template void DynamicQuantCacheKernel( const GPUContext &dev_ctx, const DenseTensor &qkv, // [token_num, 2 * q_num_head + kv_num_head, head_dim] const DenseTensor &block_tables, const DenseTensor &padding_offsets, const DenseTensor &seq_lens, const DenseTensor &k_quant_scales, const DenseTensor &v_quant_scales, const DenseTensor &k_dequant_scales, const DenseTensor &v_dequant_scales, const optional &pre_key_cache, const optional &pre_value_cache, const int batch_size, const int q_num_heads, const int kv_num_heads, const int head_size, const int max_seq_len, const int pre_cache_length, DenseTensor *key_cache_out, DenseTensor *value_cache_out) { typedef PDDataTypeTraits traits_; typedef typename traits_::DataType DataType_; // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t num_tokens = padding_offsets.dims()[0]; // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t max_blocks_per_seq = block_tables.dims()[1]; // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t block_size = key_cache_out->dims()[2]; constexpr int PackSize = 16 / sizeof(T); assert(head_size % PackSize == 0); const DataType_ *qkv_ptr = reinterpret_cast(qkv.data()); // [max_block_num, num_head, block_size, head_dim] uint8_t *cache_k_ptr = key_cache_out->data(); uint8_t *cache_v_ptr = value_cache_out->data(); float *k_quant_scales_data = const_cast(k_quant_scales.data()); float *k_dequant_scales_data = const_cast(k_dequant_scales.data()); float *v_quant_scales_data = const_cast(v_quant_scales.data()); float *v_dequant_scales_data = const_cast(v_dequant_scales.data()); constexpr int block_sz = 1024; // TODO(large-tensor): downstream functors may still use int; guard until // upgraded. int64_t bsz = seq_lens.dims()[0]; dim3 grid(kv_num_heads, bsz, 2); // [token_num, (2 * kv_num_head + q_num_head), head_dim/x, x]->[max_block_num, // kv_num_head, block_size, head_dim/x, x] Quant and Write kv quant_write_cache_int8_kernel <<>>(qkv_ptr, cache_k_ptr, cache_v_ptr, block_tables.data(), padding_offsets.data(), seq_lens.data(), max_seq_len, pre_cache_length, max_blocks_per_seq, num_tokens, q_num_heads, kv_num_heads, head_size, block_size, k_quant_scales_data, v_quant_scales_data, k_dequant_scales_data, v_dequant_scales_data); if (pre_key_cache) { // stage 2: write pre_cache to cache [:pre_cache_length] const int elem_nums = batch_size * kv_num_heads * pre_cache_length * head_size * 2; const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); write_pre_cache_int8_to_cache <<>>( key_cache_out->data(), value_cache_out->data(), reinterpret_cast(pre_key_cache.get().data()), reinterpret_cast( pre_value_cache.get().data()), block_tables.data(), seq_lens.data(), k_quant_scales.data(), v_quant_scales.data(), max_seq_len, max_blocks_per_seq, kv_num_heads, head_size, block_size, pre_cache_length, elem_nums, 1, 127.0f, -127.0f); } } template __global__ void VariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, T *qkv_out, const int64_t elem_cnt, const int num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; constexpr int HalfVecSize = VecSize / 2; using LoadEmbT = AlignedVector; LoadT src_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int hidden_size = num_head * last_dim; const int offset = 2 * hidden_size; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int qkv_id = bias / hidden_size; const int qkv_bias = bias % hidden_size; const int hi = qkv_bias / last_dim; const int h_bias = qkv_bias % last_dim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * half_lastdim + h_bias / 2; const int64_t base_idx = token_idx * 3 * hidden_size + qkv_id * hidden_size + hi * last_dim + h_bias; Load(&qkv[base_idx], &src_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < HalfVecSize; i++) { const float input_left = static_cast(src_vec[2 * i]); const float input_right = static_cast(src_vec[2 * i + 1]); const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; src_vec[2 * i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); src_vec[2 * i + 1] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } Store(src_vec, &qkv_out[base_idx]); } } template __global__ void NeoxVariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, T *qkv_out, const int64_t elem_cnt, const int num_head, const int seq_len, const int last_dim) { // [token_num, 2, num_head, dim_head / 2] using LoadT = AlignedVector; using LoadEmbT = AlignedVector; LoadT left_vec; LoadT right_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int hidden_size = num_head * half_lastdim; const int full_hidden_size = num_head * last_dim; const int offset = 2 * hidden_size; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int qkv_id = bias / hidden_size; const int qkv_bias = bias % hidden_size; const int hi = qkv_bias / half_lastdim; const int h_bias = qkv_bias % half_lastdim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * last_dim + h_bias; const int base_idx_left = token_idx * 3 * full_hidden_size + qkv_id * full_hidden_size + hi * last_dim + h_bias; const int base_idx_right = base_idx_left + half_lastdim; Load(&qkv[base_idx_left], &left_vec); Load(&qkv[base_idx_right], &right_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < VecSize; i++) { const float input_left = static_cast(left_vec[i]); const float input_right = static_cast(right_vec[i]); const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; left_vec[i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); right_vec[i] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } Store(left_vec, &qkv_out[base_idx_left]); Store(right_vec, &qkv_out[base_idx_right]); } } template void rotary_qk_variable( const GPUContext &dev_ctx, T *qkv, // [token_num, 3, num_head, dim_head] const T *qkv_input, // qkv const float *rotary_emb, // [2, 1, 1, seq_len, dim_head / 2] const int *padding_offsets, const int *seq_lens, const int token_num, const int head_num, const int seq_len, const int input_output_len, const int dim_head, bool use_neox_style = false) { int elem_nums = token_num * 2 * head_num * dim_head; // just q and k if (use_neox_style) { elem_nums = token_num * head_num * dim_head; } constexpr int PackSize = 16 / sizeof(T); const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (!use_neox_style) { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head / 2; VariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv, elem_nums, head_num, seq_len, dim_head); } else { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head; NeoxVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv, elem_nums, head_num, seq_len, dim_head); } } template __global__ void GQAVariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, T *qkv_out, const int64_t elem_cnt, const int q_num_head, const int kv_num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; constexpr int HalfVecSize = VecSize / 2; using LoadEmbT = AlignedVector; LoadT src_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int offset = (q_num_head + kv_num_head) * last_dim; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int hi = bias / last_dim; const int h_bias = bias % last_dim; const int ori_seq_id = ori_token_idx % seq_len; const int64_t emb_idx = ori_seq_id * half_lastdim + h_bias / 2; // [token_num, q_num_head + 2 * kv_num_head, last_dim] const int64_t base_idx = token_idx * (q_num_head + 2 * kv_num_head) * last_dim + hi * last_dim + h_bias; Load(&qkv[base_idx], &src_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < HalfVecSize; i++) { const float input_left = static_cast(src_vec[2 * i]); const float input_right = static_cast(src_vec[2 * i + 1]); const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; src_vec[2 * i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); src_vec[2 * i + 1] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } Store(src_vec, &qkv_out[base_idx]); } } template __global__ void GQANeoxVariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, T *qkv_out, const int64_t elem_cnt, const int q_num_head, const int kv_num_head, const int seq_len, const int last_dim) { // [token_num, 2, q_num_head, dim_head / 2] using LoadT = AlignedVector; using LoadEmbT = AlignedVector; LoadT left_vec; LoadT right_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int offset = (q_num_head + kv_num_head) * half_lastdim; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int hi = bias / half_lastdim; const int h_bias = bias % half_lastdim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * last_dim + h_bias; const int base_idx_left = token_idx * (q_num_head + 2 * kv_num_head) * last_dim + hi * last_dim + h_bias; const int base_idx_right = base_idx_left + half_lastdim; Load(&qkv[base_idx_left], &left_vec); Load(&qkv[base_idx_right], &right_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < VecSize; i++) { const float input_left = static_cast(left_vec[i]); const float input_right = static_cast(right_vec[i]); const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; left_vec[i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); right_vec[i] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } Store(left_vec, &qkv_out[base_idx_left]); Store(right_vec, &qkv_out[base_idx_right]); } } template void gqa_rotary_qk_variable( const GPUContext &dev_ctx, T *qkv, // [token_num, 3, q_num_head, dim_head] const T *qkv_input, // qkv const float *rotary_emb, // [2, 1, 1, seq_len, dim_head / 2] const int *padding_offsets, const int *seq_lens, const int token_num, const int q_head_num, const int kv_head_num, const int seq_len, const int input_output_len, const int dim_head, bool use_neox_style = false) { int elem_nums = token_num * (q_head_num + kv_head_num) * dim_head; // just q and k if (use_neox_style) { elem_nums /= 2; } constexpr int PackSize = 16 / sizeof(T); const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (!use_neox_style) { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head / 2; GQAVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv, elem_nums, q_head_num, kv_head_num, seq_len, dim_head); } else { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head; GQANeoxVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv, elem_nums, q_head_num, kv_head_num, seq_len, dim_head); } } template __global__ void VariableLengthRotaryKernel( const int *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const float *qkv_out_scales, // [3, num_head, dim_head] const T *qkv_biases, // [3, num_head, dim_head] T *qkv_out, const int64_t elem_cnt, const int num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; using LoadBiasT = AlignedVector; using LoadScaleT = AlignedVector; constexpr int HalfVecSize = VecSize / 2; using LoadEmbT = AlignedVector; LoadT src_vec; LoadBiasT bias_vec; LoadScaleT out_scale_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int hidden_size = num_head * last_dim; const int offset = 3 * hidden_size; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int qkv_id = bias / hidden_size; const int qkv_bias = bias % hidden_size; const int hi = qkv_bias / last_dim; const int h_bias = qkv_bias % last_dim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * half_lastdim + h_bias / 2; const int bias_idx = qkv_id * hidden_size + hi * last_dim + h_bias; const int64_t base_idx = token_idx * 3 * hidden_size + bias_idx; Load(&qkv[base_idx], &src_vec); Load(&qkv_biases[bias_idx], &bias_vec); Load(&qkv_out_scales[bias_idx], &out_scale_vec); if (qkv_id < 2) { Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); } #pragma unroll for (int i = 0; i < HalfVecSize; i++) { float input_left = static_cast(src_vec[2 * i]); float input_right = static_cast(src_vec[2 * i + 1]); // dequant + bias_add input_left = input_left * out_scale_vec[2 * i] + static_cast(bias_vec[2 * i]); input_right = input_right * out_scale_vec[2 * i + 1] + static_cast(bias_vec[2 * i + 1]); if (qkv_id < 2) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; bias_vec[2 * i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); bias_vec[2 * i + 1] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { bias_vec[2 * i] = static_cast(input_left); bias_vec[2 * i + 1] = static_cast(input_right); } } Store(bias_vec, &qkv_out[base_idx]); } } template __global__ void NeoxVariableLengthRotaryKernel( const int *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const float *qkv_out_scales, // [3, num_head, dim_head] const T *qkv_biases, // [3, num_head, dim_head] T *qkv_out, const int64_t elem_cnt, const int num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; using LoadBiasT = AlignedVector; using LoadScaleT = AlignedVector; using LoadEmbT = AlignedVector; LoadT left_vec; LoadT right_vec; LoadBiasT left_bias_vec; LoadBiasT right_bias_vec; LoadScaleT left_out_scale_vec; LoadScaleT right_out_scale_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int hidden_size = num_head * half_lastdim; const int full_hidden_size = num_head * last_dim; const int offset = 3 * hidden_size; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int qkv_id = bias / hidden_size; const int qkv_bias = bias % hidden_size; const int hi = qkv_bias / half_lastdim; const int h_bias = qkv_bias % half_lastdim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * last_dim + h_bias; const int bias_idx_left = qkv_id * full_hidden_size + hi * last_dim + h_bias; const int bias_idx_right = bias_idx_left + half_lastdim; const int base_idx_left = token_idx * 3 * full_hidden_size + bias_idx_left; const int base_idx_right = base_idx_left + half_lastdim; Load(&qkv[base_idx_left], &left_vec); Load(&qkv[base_idx_right], &right_vec); Load(&qkv_biases[bias_idx_left], &left_bias_vec); Load(&qkv_biases[bias_idx_right], &right_bias_vec); Load(&qkv_out_scales[bias_idx_left], &left_out_scale_vec); Load(&qkv_out_scales[bias_idx_right], &right_out_scale_vec); if (qkv_id < 2) { Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); } #pragma unroll for (int i = 0; i < VecSize; i++) { float input_left = static_cast(left_vec[i]); float input_right = static_cast(right_vec[i]); // dequant + bias_add input_left = input_left * left_out_scale_vec[i] + static_cast(left_bias_vec[i]); input_right = input_right * right_out_scale_vec[i] + static_cast(right_bias_vec[i]); if (qkv_id < 2) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; left_bias_vec[i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); right_bias_vec[i] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { left_bias_vec[i] = static_cast(input_left); right_bias_vec[i] = static_cast(input_right); } } Store(left_bias_vec, &qkv_out[base_idx_left]); Store(right_bias_vec, &qkv_out[base_idx_right]); } } template void rotary_qk_variable( const GPUContext &dev_ctx, T *qkv, // [token_num, 3, num_head, dim_head] const int *qkv_input, // qkv const float *qkv_out_scales, // [3, num_head, dim_head] const T *qkv_bias, const float *rotary_emb, // [2, 1, 1, seq_len, dim_head / 2] const int *padding_offsets, const int *seq_lens, const int token_num, const int head_num, const int seq_len, const int input_output_len, const int dim_head, bool use_neox_style = false) { int elem_nums = token_num * 3 * head_num * dim_head; // just q and k if (use_neox_style) { elem_nums = token_num * 3 * head_num * dim_head / 2; } constexpr int PackSize = 16 / sizeof(T); const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (!use_neox_style) { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head / 2; VariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_out_scales, qkv_bias, qkv, elem_nums, head_num, seq_len, dim_head); } else { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head; NeoxVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_out_scales, qkv_bias, qkv, elem_nums, head_num, seq_len, dim_head); } } template __global__ void GQAVariableLengthRotaryKernel( const int *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const float *qkv_out_scales, // [3, q_num_head, dim_head] const T *qkv_biases, // [3, q_num_head, dim_head] T *qkv_out, const int64_t elem_cnt, const int q_num_head, const int kv_num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; using LoadBiasT = AlignedVector; using LoadScaleT = AlignedVector; constexpr int HalfVecSize = VecSize / 2; using LoadEmbT = AlignedVector; LoadT src_vec; LoadBiasT bias_vec; LoadScaleT out_scale_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int offset = (q_num_head + 2 * kv_num_head) * last_dim; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int hi = bias / last_dim; const int h_bias = bias % last_dim; const int ori_seq_id = ori_token_idx % seq_len; const int64_t emb_idx = ori_seq_id * half_lastdim + h_bias / 2; const int64_t bias_idx = hi * last_dim + h_bias; const int64_t base_idx = token_idx * offset + bias_idx; Load(&qkv[base_idx], &src_vec); Load(&qkv_biases[bias_idx], &bias_vec); Load(&qkv_out_scales[bias_idx], &out_scale_vec); if (hi < q_num_head + kv_num_head) { Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); } #pragma unroll for (int i = 0; i < HalfVecSize; i++) { float input_left = static_cast(src_vec[2 * i]); float input_right = static_cast(src_vec[2 * i + 1]); // dequant + bias_add input_left = input_left * out_scale_vec[2 * i] + static_cast(bias_vec[2 * i]); input_right = input_right * out_scale_vec[2 * i + 1] + static_cast(bias_vec[2 * i + 1]); if (hi < q_num_head + kv_num_head) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; bias_vec[2 * i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); bias_vec[2 * i + 1] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { bias_vec[2 * i] = static_cast(input_left); bias_vec[2 * i + 1] = static_cast(input_right); } } Store(bias_vec, &qkv_out[base_idx]); } } template __global__ void GQANeoxVariableLengthRotaryKernel( const int *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const float *qkv_out_scales, // [3, q_num_head, dim_head] const T *qkv_biases, // [3, q_num_head, dim_head] T *qkv_out, const int64_t elem_cnt, const int q_num_head, const int kv_num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; using LoadBiasT = AlignedVector; using LoadScaleT = AlignedVector; using LoadEmbT = AlignedVector; LoadT left_vec; LoadT right_vec; LoadBiasT left_bias_vec; LoadBiasT right_bias_vec; LoadScaleT left_out_scale_vec; LoadScaleT right_out_scale_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int offset = (q_num_head + 2 * kv_num_head) * half_lastdim; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int hi = bias / half_lastdim; const int h_bias = bias % half_lastdim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * last_dim + h_bias; const int bias_idx_left = hi * last_dim + h_bias; const int bias_idx_right = bias_idx_left + half_lastdim; const int base_idx_left = token_idx * offset + bias_idx_left; const int base_idx_right = base_idx_left + half_lastdim; Load(&qkv[base_idx_left], &left_vec); Load(&qkv[base_idx_right], &right_vec); Load(&qkv_biases[bias_idx_left], &left_bias_vec); Load(&qkv_biases[bias_idx_right], &right_bias_vec); Load(&qkv_out_scales[bias_idx_left], &left_out_scale_vec); Load(&qkv_out_scales[bias_idx_right], &right_out_scale_vec); if (hi < (q_num_head + kv_num_head)) { Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); } #pragma unroll for (int i = 0; i < VecSize; i++) { float input_left = static_cast(left_vec[i]); float input_right = static_cast(right_vec[i]); // dequant + bias_add input_left = input_left * left_out_scale_vec[i] + static_cast(left_bias_vec[i]); input_right = input_right * right_out_scale_vec[i] + static_cast(right_bias_vec[i]); if (hi < (q_num_head + kv_num_head)) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; left_bias_vec[i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); right_bias_vec[i] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { left_bias_vec[i] = static_cast(input_left); right_bias_vec[i] = static_cast(input_right); } } Store(left_bias_vec, &qkv_out[base_idx_left]); Store(right_bias_vec, &qkv_out[base_idx_right]); } } template void gqa_rotary_qk_variable( const GPUContext &dev_ctx, T *qkv, // [token_num, 3, q_num_head, dim_head] const int *qkv_input, // qkv const float *qkv_out_scales, // [3, q_num_head, dim_head] const T *qkv_bias, const float *rotary_emb, // [2, 1, 1, seq_len, dim_head / 2] const int *padding_offsets, const int *seq_lens, const int token_num, const int q_head_num, const int kv_head_num, const int seq_len, const int input_output_len, const int dim_head, bool use_neox_style = false) { int elem_nums = token_num * (q_head_num + 2 * kv_head_num) * dim_head; // for all q k v if (use_neox_style) { elem_nums /= 2; } constexpr int PackSize = 16 / sizeof(T); const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (!use_neox_style) { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head / 2; GQAVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_out_scales, qkv_bias, qkv, elem_nums, q_head_num, kv_head_num, seq_len, dim_head); } else { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head; GQANeoxVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_out_scales, qkv_bias, qkv, elem_nums, q_head_num, kv_head_num, seq_len, dim_head); } } template __global__ void VariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const T *qkv_biases, T *qkv_out, const int64_t elem_cnt, const int num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; constexpr int HalfVecSize = VecSize / 2; using LoadEmbT = AlignedVector; LoadT src_vec; LoadT bias_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int hidden_size = num_head * last_dim; const int offset = 3 * hidden_size; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int qkv_id = bias / hidden_size; const int qkv_bias = bias % hidden_size; const int hi = qkv_bias / last_dim; const int h_bias = qkv_bias % last_dim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * half_lastdim + h_bias / 2; const int bias_idx = qkv_id * hidden_size + hi * last_dim + h_bias; const int64_t base_idx = token_idx * 3 * hidden_size + bias_idx; Load(&qkv[base_idx], &src_vec); Load(&qkv_biases[bias_idx], &bias_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < HalfVecSize; i++) { const float input_left = static_cast(src_vec[2 * i] + bias_vec[2 * i]); const float input_right = static_cast(src_vec[2 * i + 1] + bias_vec[2 * i + 1]); if (qkv_id < 2) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; src_vec[2 * i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); src_vec[2 * i + 1] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { src_vec[2 * i] = static_cast(input_left); src_vec[2 * i + 1] = static_cast(input_right); } } Store(src_vec, &qkv_out[base_idx]); } } template __global__ void NeoxVariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const T *qkv_biases, T *qkv_out, const int64_t elem_cnt, const int num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; using LoadEmbT = AlignedVector; LoadT left_vec; LoadT right_vec; LoadT left_bias_vec; LoadT right_bias_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int hidden_size = num_head * half_lastdim; const int full_hidden_size = num_head * last_dim; const int offset = 3 * hidden_size; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int qkv_id = bias / hidden_size; const int qkv_bias = bias % hidden_size; const int hi = qkv_bias / half_lastdim; const int h_bias = qkv_bias % half_lastdim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * last_dim + h_bias; const int bias_idx_left = qkv_id * full_hidden_size + hi * last_dim + h_bias; const int bias_idx_right = bias_idx_left + half_lastdim; const int base_idx_left = token_idx * 3 * full_hidden_size + bias_idx_left; const int base_idx_right = base_idx_left + half_lastdim; Load(&qkv[base_idx_left], &left_vec); Load(&qkv[base_idx_right], &right_vec); Load(&qkv_biases[bias_idx_left], &left_bias_vec); Load(&qkv_biases[bias_idx_right], &right_bias_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < VecSize; i++) { const float input_left = static_cast(left_vec[i] + left_bias_vec[i]); const float input_right = static_cast(right_vec[i] + right_bias_vec[i]); if (qkv_id < 2) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; left_vec[i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); right_vec[i] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { left_vec[i] = static_cast(input_left); right_vec[i] = static_cast(input_right); } } Store(left_vec, &qkv_out[base_idx_left]); Store(right_vec, &qkv_out[base_idx_right]); } } template void rotary_qk_variable( const GPUContext &dev_ctx, T *qkv, // [token_num, 3, num_head, dim_head] const T *qkv_input, // qkv const T *qkv_bias, const float *rotary_emb, // [2, 1, 1, seq_len, dim_head / 2] const int *padding_offsets, const int *seq_lens, const int token_num, const int head_num, const int seq_len, const int input_output_len, const int dim_head, bool use_neox_style = false) { int elem_nums = token_num * 3 * head_num * dim_head; // just q and k if (use_neox_style) { elem_nums = token_num * 3 * head_num * dim_head / 2; } constexpr int PackSize = 16 / sizeof(T); const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (!use_neox_style) { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head / 2; VariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_bias, qkv, elem_nums, head_num, seq_len, dim_head); } else { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head; NeoxVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_bias, qkv, elem_nums, head_num, seq_len, dim_head); } } template __global__ void GQAVariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const T *qkv_biases, T *qkv_out, const int64_t elem_cnt, const int q_num_head, const int kv_num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; constexpr int HalfVecSize = VecSize / 2; using LoadEmbT = AlignedVector; LoadT src_vec; LoadT bias_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int offset = (q_num_head + 2 * kv_num_head) * last_dim; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int hi = bias / last_dim; const int h_bias = bias % last_dim; const int ori_seq_id = ori_token_idx % seq_len; const int64_t emb_idx = ori_seq_id * half_lastdim + h_bias / 2; const int64_t bias_idx = hi * last_dim + h_bias; const int64_t base_idx = token_idx * offset + bias_idx; Load(&qkv[base_idx], &src_vec); Load(&qkv_biases[bias_idx], &bias_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < HalfVecSize; i++) { const float input_left = static_cast(src_vec[2 * i] + bias_vec[2 * i]); const float input_right = static_cast(src_vec[2 * i + 1] + bias_vec[2 * i + 1]); if (hi < q_num_head + kv_num_head) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; src_vec[2 * i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); src_vec[2 * i + 1] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { src_vec[2 * i] = static_cast(input_left); src_vec[2 * i + 1] = static_cast(input_right); } } Store(src_vec, &qkv_out[base_idx]); } } template __global__ void GQANeoxVariableLengthRotaryKernel( const T *qkv, const float *cos_emb, // [1, 1, seq_len, dim_head / 2] const float *sin_emb, const int *padding_offsets, const int *seq_lens, const T *qkv_biases, T *qkv_out, const int64_t elem_cnt, const int q_num_head, const int kv_num_head, const int seq_len, const int last_dim) { using LoadT = AlignedVector; using LoadEmbT = AlignedVector; LoadT left_vec; LoadT right_vec; LoadT left_bias_vec; LoadT right_bias_vec; LoadEmbT cos_emb_vec; LoadEmbT sin_emb_vec; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int half_lastdim = last_dim / 2; const int offset = (q_num_head + 2 * kv_num_head) * half_lastdim; for (int64_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / offset; const int ori_token_idx = token_idx + padding_offsets[token_idx]; const int ori_bi = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_bi] == 0) continue; const int bias = linear_index % offset; const int hi = bias / half_lastdim; const int h_bias = bias % half_lastdim; const int ori_seq_id = ori_token_idx % seq_len; const int emb_idx = ori_seq_id * last_dim + h_bias; const int bias_idx_left = hi * last_dim + h_bias; const int bias_idx_right = bias_idx_left + half_lastdim; const int base_idx_left = token_idx * offset + bias_idx_left; const int base_idx_right = base_idx_left + half_lastdim; Load(&qkv[base_idx_left], &left_vec); Load(&qkv[base_idx_right], &right_vec); Load(&qkv_biases[bias_idx_left], &left_bias_vec); Load(&qkv_biases[bias_idx_right], &right_bias_vec); Load(&cos_emb[emb_idx], &cos_emb_vec); Load(&sin_emb[emb_idx], &sin_emb_vec); #pragma unroll for (int i = 0; i < VecSize; i++) { const float input_left = static_cast(left_vec[i] + left_bias_vec[i]); const float input_right = static_cast(right_vec[i] + right_bias_vec[i]); if (hi < (q_num_head + kv_num_head)) { // qk rope const float cos_tmp = cos_emb_vec[i]; const float sin_tmp = sin_emb_vec[i]; left_vec[i] = static_cast(input_left * cos_tmp - input_right * sin_tmp); right_vec[i] = static_cast(input_right * cos_tmp + input_left * sin_tmp); } else { left_vec[i] = static_cast(input_left); right_vec[i] = static_cast(input_right); } } Store(left_vec, &qkv_out[base_idx_left]); Store(right_vec, &qkv_out[base_idx_right]); } } template void gqa_rotary_qk_variable( const GPUContext &dev_ctx, T *qkv, // [token_num, 3, q_num_head, dim_head] const T *qkv_input, // qkv const T *qkv_bias, const float *rotary_emb, // [2, 1, 1, seq_len, dim_head / 2] const int *padding_offsets, const int *seq_lens, const int token_num, const int q_head_num, const int kv_head_num, const int seq_len, const int input_output_len, const int dim_head, bool use_neox_style = false) { int elem_nums = token_num * (q_head_num + 2 * kv_head_num) * dim_head; // for all q k v if (use_neox_style) { elem_nums /= 2; } constexpr int PackSize = 16 / sizeof(T); const int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); if (!use_neox_style) { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head / 2; GQAVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_bias, qkv, elem_nums, q_head_num, kv_head_num, seq_len, dim_head); } else { const float *cos_emb = rotary_emb; const float *sin_emb = rotary_emb + input_output_len * dim_head; GQANeoxVariableLengthRotaryKernel <<>>(qkv_input, cos_emb, sin_emb, padding_offsets, seq_lens, qkv_bias, qkv, elem_nums, q_head_num, kv_head_num, seq_len, dim_head); } } template __global__ void ShiftSmoothQuant(const T *input, const T *shift, const T *smooth, float scale, int8_t *out, int num, int cols, float quant_max_bound, float quant_min_bound) { AlignedVector in_vec; AlignedVector shift_vec; AlignedVector smooth_vec; AlignedVector out_vec; for (int64_t linear_id = static_cast(blockIdx.x) * static_cast(blockDim.x) + static_cast(threadIdx.x); linear_id * VecSize < num; linear_id += gridDim.x * blockDim.x) { int idx = linear_id * VecSize; Load(input + idx, &in_vec); Load(shift + (idx % cols), &shift_vec); Load(smooth + (idx % cols), &smooth_vec); #pragma unroll for (int i = 0; i < VecSize; i++) { float quant_value = quant_max_bound * static_cast((in_vec[i] + shift_vec[i]) * smooth_vec[i]) * scale; quant_value = static_cast(RoundType == 1 ? round(quant_value) : rintf(quant_value)); quant_value = quant_value > quant_max_bound ? quant_max_bound : quant_value; quant_value = quant_value < quant_min_bound ? quant_min_bound : quant_value; out_vec[i] = static_cast(quant_value); } Store(out_vec, out + idx); } } template __global__ void ShiftSmooth(const T *input, const T *shift, const T *smooth, T *out, int num, int cols) { AlignedVector in_vec; AlignedVector shift_vec; AlignedVector smooth_vec; AlignedVector out_vec; for (int64_t linear_id = static_cast(blockIdx.x) * static_cast(blockDim.x) + static_cast(threadIdx.x); linear_id * VecSize < num; linear_id += gridDim.x * blockDim.x) { int idx = linear_id * VecSize; Load(input + idx, &in_vec); Load(shift + (idx % cols), &shift_vec); Load(smooth + (idx % cols), &smooth_vec); #pragma unroll for (int i = 0; i < VecSize; i++) { out_vec[i] = (in_vec[i] + shift_vec[i]) * smooth_vec[i]; } Store(out_vec, out + idx); } } template void shift_smooth_quant(const GPUContext &dev_ctx, DenseTensor *fmha_out, const DenseTensor &fmha_in, const DenseTensor &out_linear_shift, const DenseTensor &out_linear_smooth, float out_linear_in_scale, const int num_head, const int dim_head, const int quant_round_type, const float quant_max_bound, const float quant_min_bound) { constexpr int block_size = 512; constexpr int waves = 32; constexpr int vec_size = 16 / sizeof(T); int max_blocks = fmha_out->numel() / vec_size; int num_blocks = 0; if (out_linear_in_scale > 0) { if (quant_round_type == 0) { GetNumBlocks(ShiftSmoothQuant, block_size, 0, max_blocks, waves, &num_blocks); ShiftSmoothQuant <<>>( fmha_in.data(), out_linear_shift.data(), out_linear_smooth.data(), out_linear_in_scale, fmha_out->data(), fmha_out->numel(), num_head * dim_head, quant_max_bound, quant_min_bound); } else { GetNumBlocks(ShiftSmoothQuant, block_size, 0, max_blocks, waves, &num_blocks); ShiftSmoothQuant <<>>( fmha_in.data(), out_linear_shift.data(), out_linear_smooth.data(), out_linear_in_scale, fmha_out->data(), fmha_out->numel(), num_head * dim_head, quant_max_bound, quant_min_bound); } } else { if (quant_round_type == 0) { GetNumBlocks(ShiftSmooth, block_size, 0, max_blocks, waves, &num_blocks); ShiftSmooth <<>>( fmha_in.data(), out_linear_shift.data(), out_linear_smooth.data(), fmha_out->data(), fmha_out->numel(), num_head * dim_head); } else { GetNumBlocks(ShiftSmooth, block_size, 0, max_blocks, waves, &num_blocks); ShiftSmooth <<>>( fmha_in.data(), out_linear_shift.data(), out_linear_smooth.data(), fmha_out->data(), fmha_out->numel(), num_head * dim_head); } } } template __global__ void fusedQKV_transpose_split_kernel(T *q_buf, T *k_buf, T *v_buf, const T *qkv, const int *padding_offset, const int *seq_lens, const int32_t elem_cnt, const int batch_size, const int seq_len, const int token_num, const int q_head_num, const int kv_head_num, const int size_per_head) { int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); using LoadT = AlignedVector; LoadT src_vec; const int fused_hidden_size = (q_head_num + 2 * kv_head_num) * size_per_head; for (int32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { Load(&qkv[linear_index], &src_vec); int32_t bias_idx = linear_index % fused_hidden_size; const int32_t token_idx = linear_index / fused_hidden_size; const int32_t ori_token_idx = token_idx + (padding_offset == nullptr ? 0 : padding_offset[token_idx]); const int32_t target_batch_id = ori_token_idx / seq_len; if (seq_lens[target_batch_id] == 0) continue; const int32_t head_id = bias_idx / size_per_head; const int32_t size_id = linear_index % size_per_head; // [token_num, q_head_num or kv_head_num, size_per_head] if (head_id < q_head_num) { const int32_t write_idx = token_idx * q_head_num * size_per_head + head_id * size_per_head + size_id; Store(src_vec, &q_buf[write_idx]); } else { if (head_id < q_head_num + kv_head_num) { const int32_t write_idx = token_idx * kv_head_num * size_per_head + (head_id - q_head_num) * size_per_head + size_id; Store(src_vec, &k_buf[write_idx]); } else { const int32_t write_idx = token_idx * kv_head_num * size_per_head + (head_id - q_head_num - kv_head_num) * size_per_head + size_id; Store(src_vec, &v_buf[write_idx]); } } } } template void qkv_transpose_split(const GPUContext &dev_ctx, T *q_buf, T *k_buf, T *v_buf, const T *qkv, const int *padding_offset, const int *seq_lens, const int token_num, const int batch_size, const int q_head_num, const int kv_head_num, const int seq_len, const int size_per_head) { const int32_t elem_cnt = token_num * (q_head_num + kv_head_num * 2) * size_per_head; constexpr int PackSize = VEC_16B / sizeof(T); PADDLE_ENFORCE_EQ(size_per_head % PackSize, 0, common::errors::PreconditionNotMet( "dim_head=%d must be divisible by vec_size=%d", size_per_head, PackSize)); const int32_t pack_num = elem_cnt / PackSize; const int32_t blocksize = 128; int32_t grid_size = 1; GetNumBlocks(pack_num, &grid_size); fusedQKV_transpose_split_kernel <<>>(q_buf, k_buf, v_buf, qkv, padding_offset, seq_lens, elem_cnt, batch_size, seq_len, token_num, q_head_num, kv_head_num, size_per_head); } template __global__ void write_pre_cache_to_kv_buffer( T *k_buf, // [bsz, num_head, seq_len + pre_cache_length, head_dim] T *v_buf, const T *pre_key_cache, // [bsz, num_head, pre_cache_length, head_dim] const T *pre_value_cache, const int *seq_lens, const int batch_size, const int pre_cache_length, const int num_head, const int head_dim, const int max_len_this_time, const int elem_cnt) { const int32_t hidden_size = pre_cache_length * head_dim; const int32_t cache_hidden_size = num_head * hidden_size; const int32_t fused_hidden_size = 2 * cache_hidden_size; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); using LoadT = AlignedVector; LoadT src_vec; for (int32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int32_t batch_id = linear_index / fused_hidden_size; if (seq_lens[batch_id] == 0) continue; const int32_t cache_seq_id = (linear_index % hidden_size) / head_dim; const int32_t head_id = (linear_index % cache_hidden_size) / hidden_size; const int32_t size_id = linear_index % head_dim; const int32_t kv_id = (linear_index % fused_hidden_size) / cache_hidden_size; const int32_t read_id = batch_id * cache_hidden_size + head_id * hidden_size + cache_seq_id * head_dim + size_id; if (kv_id == 0) { Load(&pre_key_cache[read_id], &src_vec); } else { Load(&pre_value_cache[read_id], &src_vec); } const int tmp_max_len_this_time = max_len_this_time + pre_cache_length; const int32_t write_idx = batch_id * num_head * tmp_max_len_this_time * head_dim + head_id * tmp_max_len_this_time * head_dim + cache_seq_id * head_dim + size_id; if (kv_id == 0) { Store(src_vec, &k_buf[write_idx]); } else { Store(src_vec, &v_buf[write_idx]); } } } template __global__ void fusedQKV_transpose_split_kernel(T *q_buf, T *k_buf, T *v_buf, const T *qkv, const int *padding_offset, const int *seq_lens, const int32_t elem_cnt, const int batch_size, const int max_len_this_time, const int seq_len, const int pre_cache_length, const int token_num, const int q_head_num, const int kv_head_num, const int size_per_head) { const int fused_hidden_size = (q_head_num + 2 * kv_head_num) * size_per_head; int64_t global_thread_idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); using LoadT = AlignedVector; LoadT src_vec; for (int32_t linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { Load(&qkv[linear_index], &src_vec); int32_t bias_idx = linear_index % fused_hidden_size; const int32_t token_idx = linear_index / fused_hidden_size; const int32_t ori_token_idx = token_idx + (padding_offset == nullptr ? 0 : padding_offset[token_idx]); const int32_t target_batch_id = ori_token_idx / seq_len; if (seq_lens[target_batch_id] == 0) continue; const int32_t seq_id = ori_token_idx % seq_len; const int32_t head_id = bias_idx / size_per_head; const int32_t size_id = linear_index % size_per_head; const int tmp_max_len_this_time = max_len_this_time + (head_id < q_head_num ? 0 : pre_cache_length); const int tmp_seq_id = head_id < q_head_num ? seq_id : seq_id + pre_cache_length; if (head_id < q_head_num) { const int write_idx = target_batch_id * q_head_num * tmp_max_len_this_time * size_per_head + head_id * tmp_max_len_this_time * size_per_head + tmp_seq_id * size_per_head + size_id; Store(src_vec, &q_buf[write_idx]); } else if (head_id < q_head_num + kv_head_num) { const int write_idx = target_batch_id * kv_head_num * tmp_max_len_this_time * size_per_head + (head_id - q_head_num) * tmp_max_len_this_time * size_per_head + tmp_seq_id * size_per_head + size_id; Store(src_vec, &k_buf[write_idx]); } else { const int write_idx = target_batch_id * kv_head_num * tmp_max_len_this_time * size_per_head + (head_id - q_head_num - kv_head_num) * tmp_max_len_this_time * size_per_head + tmp_seq_id * size_per_head + size_id; Store(src_vec, &v_buf[write_idx]); } } } template void qkv_transpose_split( const GPUContext &dev_ctx, T *q_buf, T *k_buf, T *v_buf, const T *qkv, const T *pre_key_cache, // [bsz, num_head, pre_cache_length, head_dim] const T *pre_value_cache, const int *padding_offset, const int *seq_lens, const int token_num, const int batch_size, const int q_head_num, const int kv_head_num, const int max_len_this_time, const int seq_len, const int pre_cache_length, const int size_per_head) { int32_t elem_cnt = token_num * (q_head_num + kv_head_num * 2) * size_per_head; constexpr int PackSize = VEC_16B / sizeof(T); PADDLE_ENFORCE_EQ(size_per_head % PackSize, 0, common::errors::PreconditionNotMet( "dim_head=%d must be divisible by vec_size=%d", size_per_head, PackSize)); int32_t pack_num = elem_cnt / PackSize; const int32_t blocksize = 128; int32_t grid_size = 1; GetNumBlocks(pack_num, &grid_size); fusedQKV_transpose_split_kernel <<>>(q_buf, k_buf, v_buf, qkv, padding_offset, seq_lens, elem_cnt, batch_size, max_len_this_time, seq_len, pre_cache_length, token_num, q_head_num, kv_head_num, size_per_head); if (pre_key_cache) { // stage 2: write pre_cache to kv_buf elem_cnt = batch_size * q_head_num * pre_cache_length * size_per_head * 2; pack_num = elem_cnt / PackSize; GetNumBlocks(pack_num, &grid_size); write_pre_cache_to_kv_buffer <<>>(k_buf, v_buf, pre_key_cache, pre_value_cache, seq_lens, batch_size, pre_cache_length, kv_head_num, size_per_head, max_len_this_time, elem_cnt); } } template __global__ void GetDecoderTensorKernel(const T *qkv_out, const int *cum_offsets, T *qkv_out_decoder, const int token_num, const int batch_size, const int q_head_num, const int kv_head_num, const int seq_len, const int dim_head, const int elem_nums, const int qkv_out_nums) { using LoadT = AlignedVector; LoadT src_vec; const int32_t fused_hidden_size = (q_head_num + 2 * kv_head_num) * dim_head; const int global_idx = blockDim.x * blockIdx.x + threadIdx.x; for (int i = global_idx * VecSize; i < elem_nums; i += gridDim.x * blockDim.x * VecSize) { const int bi = i / fused_hidden_size; const int bias_idx = i % fused_hidden_size; const int ori_token_idx = bi * seq_len - cum_offsets[bi]; const int src_offset = ori_token_idx * fused_hidden_size + bias_idx; if (src_offset >= qkv_out_nums) continue; Load(&qkv_out[src_offset], &src_vec); Store(src_vec, &qkv_out_decoder[i]); } } template __global__ void GetDecoderRoPEKernel(const T *rope_emb, T *rope_out_emb, const int rope_bsz, const int batch_size, const int seq_len, const int dim_head, const int elem_nums) { using LoadT = AlignedVector; LoadT src_vec; const T *rope_cos_emb = rope_emb; const T *rope_sin_emb = rope_emb + rope_bsz * seq_len * dim_head; T *cos_emb = rope_out_emb; T *sin_emb = rope_out_emb + batch_size * dim_head; const int global_idx = blockDim.x * blockIdx.x + threadIdx.x; for (int i = global_idx * VecSize; i < elem_nums; i += gridDim.x * blockDim.x * VecSize) { const int bi = i / dim_head; const int src_offset = bi * seq_len * dim_head + i % dim_head; Load(&rope_cos_emb[src_offset], &src_vec); Store(src_vec, &cos_emb[i]); Load(&rope_sin_emb[src_offset], &src_vec); Store(src_vec, &sin_emb[i]); } } template void GetDecoderTensor(const GPUContext &dev_ctx, const DenseTensor &qkv_out, const DenseTensor *rope_emb, const int *cum_offsets, DenseTensor *qkv_out_decoder, DenseTensor *rope_out_emb, const int token_num, const int batch_size, const int q_num_head, const int kv_num_head, const int seq_len, const int dim_head) { // qkv_out: [token_num, 2 * kv_num_head + q_num_head, dim_head] -> [bs, 1, 2 * // kv_num_head + q_num_head, dim_head] rope: [2, bsz, 1, seq_len, dim_head] -> // [2, bsz, 1, 1, dim_head] // TODO(large-tensor): downstream functors may still use int int64_t elem_nums = qkv_out_decoder->numel(); // TODO(large-tensor): downstream functors may still use int int64_t qkv_out_nums = qkv_out.numel(); constexpr int PackSize = VEC_16B / sizeof(T); PADDLE_ENFORCE_EQ( dim_head % PackSize, 0, common::errors::PreconditionNotMet( "dim_head=%d must be divisible by vec_size=%d", dim_head, PackSize)); int pack_num = elem_nums / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); GetDecoderTensorKernel <<>>( qkv_out.data(), cum_offsets, qkv_out_decoder->data(), token_num, batch_size, q_num_head, kv_num_head, seq_len, dim_head, elem_nums, qkv_out_nums); if (rope_out_emb) { elem_nums = rope_out_emb->numel() / 2; pack_num = elem_nums / PackSize; GetNumBlocks(pack_num, &grid_size); GetDecoderRoPEKernel <<>>( rope_emb->data(), rope_out_emb->data(), rope_emb->dims()[1], batch_size, seq_len, dim_head, elem_nums); } } template struct MaxOp { __device__ __forceinline__ T operator()(const T &a, const T &b) const { return max(a, b); } }; template __global__ void GetMaxLenKernel(const int *seq_lens, int *max_len, const int64_t batch_size) { const int64_t tid = threadIdx.x; typedef cub::BlockReduce BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; int max_len_this_thread = 0; for (int64_t i = tid; i < batch_size; i += blockDim.x) { max_len_this_thread = max(seq_lens[i], max_len_this_thread); } int total = BlockReduce(temp_storage).Reduce(max_len_this_thread, MaxOp()); if (tid == 0) { *max_len = total; } } template __global__ void InitOutValueKernel(T *output_data, const int64_t numel, const T init_value) { const int tid = threadIdx.x; const int bid = blockIdx.x; int64_t global_thread_idx = bid * static_cast(blockDim.x) + tid; for (int linear_index = global_thread_idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < numel; linear_index += step) { for (int i = 0; i < VecSize; i++) { output_data[linear_index + i] = init_value; } } } template void InitValue(const GPUContext &dev_ctx, T *output_data, const int64_t numel, const T init_value) { constexpr int PackSize = VEC_16B / sizeof(T); PADDLE_ENFORCE_EQ( numel % PackSize, 0, common::errors::PreconditionNotMet( "numel=%d must be divisible by vec_size=%d", numel, PackSize)); const int pack_num = numel / PackSize; const int blocksize = 128; int grid_size = 1; GetNumBlocks(pack_num, &grid_size); InitOutValueKernel <<>>( output_data, numel, init_value); } template __global__ void TransposeRemovingPadding(const T *input_data, const int *seq_lens, T *output_data, const int batch_size, const int num_head, const int max_len_this_time, const int seq_len, const int head_dim, const int token_num, const int elem_cnt, const int *padding_offset) { // transpose and remove padding // [batch_size, num_head, max_len_this_time, head_dim] -> [token_num, // num_head, head_dim] int64_t idx = static_cast(blockDim.x) * static_cast(blockIdx.x) + static_cast(threadIdx.x); const int dim_embed = num_head * head_dim; using LoadT = AlignedVector; LoadT src_vec; for (int32_t linear_index = idx * VecSize, step = gridDim.x * blockDim.x * VecSize; linear_index < elem_cnt; linear_index += step) { const int token_idx = linear_index / dim_embed; const int ori_token_idx = token_idx + (padding_offset == nullptr ? 0 : padding_offset[token_idx]); const int ori_batch_id = ori_token_idx / seq_len; if (seq_lens && seq_lens[ori_batch_id] == 0) continue; const int ori_seq_id = ori_token_idx % seq_len; const int ori_head_id = (linear_index % dim_embed) / head_dim; const int ori_head_lane = (linear_index % dim_embed) % head_dim; const int ori_idx = ori_batch_id * num_head * max_len_this_time * head_dim + ori_head_id * max_len_this_time * head_dim + ori_seq_id * head_dim + ori_head_lane; Load(&input_data[ori_idx], &src_vec); Store(src_vec, &output_data[linear_index]); } } template void InvokeTransposeRemovePadding(const GPUContext &dev_ctx, const T *input_data, const int *seq_lens, T *output_data, const int batch_size, const int num_head, const int max_len_this_time, const int seq_len, const int head_dim, const int token_num, const int *padding_offset) { // [batch_size, num_head, max_len_this_time, head_dim] -> [token_num, // num_head, head_dim] constexpr int VEC_16B = 16; const int elem_cnt = token_num * num_head * head_dim; constexpr int PackSize = VEC_16B / sizeof(T); PADDLE_ENFORCE_EQ( head_dim % PackSize, 0, common::errors::PreconditionNotMet( "dim_head=%d must be divisible by vec_size=%d", head_dim, PackSize)); const int32_t pack_num = elem_cnt / PackSize; const int32_t block_size = 128; int32_t grid_size = (pack_num + block_size - 1) / block_size; TransposeRemovingPadding <<>>(input_data, seq_lens, output_data, batch_size, num_head, max_len_this_time, seq_len, head_dim, token_num, elem_cnt, padding_offset); } template static void VLOGMatrix(const T *mat_d, int num, std::string name, int max_num = 10) { num = num < max_num ? num : max_num; std::vector tmp(num); GPU(Memcpy)(tmp.data(), mat_d, sizeof(T) * num, GPU(MemcpyDeviceToHost)); std::stringstream ss; for (int i = 0; i < num; ++i) { if (std::is_same::value) { ss << static_cast(tmp[i]) << ", "; } else { ss << std::setprecision(8) << (float)(tmp[i]) << ", "; // NOLINT } } LOG(INFO) << "===>: " << name; LOG(INFO) << ss.str(); } } // namespace fusion } // namespace phi