// // FmhaCommonExecution.cpp // MNN // // Created by MNN on 2024/01/31. // Copyright © 2018, Alibaba Group Holding Limited // #include "FmhaCommonExecution.hpp" namespace MNN { namespace CUDA { template __global__ void SPLIT_FusedQKV(const size_t count, const T* fused_qkv, T* ptr_q, T* ptr_k, T* ptr_v, int head_size ) { for (size_t i = blockIdx.x * blockDim.x + threadIdx.x; i < (count); i += blockDim.x * gridDim.x) { //[B, S, H, 3, D] -> [B, S, H, D] const int bsh = i / head_size; const int d = i % head_size; ptr_q[i] = fused_qkv[(bsh * 3 + 0) * head_size + d]; ptr_k[i] = fused_qkv[(bsh * 3 + 1) * head_size + d]; ptr_v[i] = fused_qkv[(bsh * 3 + 2) * head_size + d]; } } template __global__ void SPLIT_FusedKV(const size_t count, const T* fused_kv, T* ptr_k, T* ptr_v, int head_size ) { for (size_t i = blockIdx.x * blockDim.x + threadIdx.x; i < (count); i += blockDim.x * gridDim.x) { //[B, S, H, 2, D] -> [B, S, H, D] const int bsh = i / head_size; const int d = i % head_size; ptr_k[i] = fused_kv[(bsh * 2 + 0) * head_size + d]; ptr_v[i] = fused_kv[(bsh * 2 + 1) * head_size + d]; } } template < int kQueriesPerBlock, int kKeysPerBlock, int kMaxK > int FmhaCommonExecution::run_attention(const std::vector &inputs, const std::vector &outputs) { using Attention = AttentionKernel< cutlass::half_t, // scalar_t cutlass::arch::Sm80, // ArchTag true, // Memory is aligned kQueriesPerBlock, kKeysPerBlock, kMaxK, false, // Supports dropout false // Supports bias >; typename Attention::Params p; // set parameters { // TODO : Split fused qkv [B, S, H, 3, D] --> [B, S, H, D] p.query_ptr = (cutlass::half_t *)mQ_Buffer;//inputs[0]->deviceId(); p.key_ptr = (cutlass::half_t *)mK_Buffer;//inputs[0]->deviceId(); p.value_ptr = (cutlass::half_t *)mV_Buffer;//inputs[0]->deviceId(); p.logsumexp_ptr = nullptr; // Only needed for bw p.output_accum_ptr = nullptr; if (Attention::kNeedsOutputAccumulatorBuffer) { p.output_accum_ptr = (float *)mAcc_Buffer; } p.output_ptr = (cutlass::half_t *)outputs[0]->deviceId(); // TODO: support arbitrary seq lengths // if (cu_seqlens_q.has_value()) { // p.cu_seqlens_q_ptr = (int32_t*)cu_seqlens_q->data_ptr(); // p.cu_seqlens_k_ptr = (int32_t*)cu_seqlens_k->data_ptr(); // } p.scale = 1.0f / sqrtf(mHeadSize); p.num_heads = mNumHeads; p.num_batches = mBatchSize; p.head_dim = mHeadSize; p.head_dim_value = mHeadSizeV; p.num_queries = mSeqLen; p.num_keys = mSeqLenKV; if (false/*options.causal*/) { p.custom_mask_type = Attention::CausalFromTopLeft; } // All tensors are in BMHK shapes p.q_strideH = mHeadSize; p.k_strideH = mHeadSize; p.v_strideH = mHeadSizeV; p.q_strideM = p.q_strideH * mNumHeads; p.k_strideM = p.k_strideH * mNumHeads; p.v_strideM = p.v_strideH * mNumHeads; p.q_strideB = p.q_strideM * mSeqLen; p.k_strideB = p.k_strideM * mSeqLenKV; p.v_strideB = p.v_strideM * mSeqLenKV; p.o_strideM = mHeadSizeV * mNumHeads; } // launch kernel :) constexpr auto kernel_fn = attention_kernel_batched_impl; int smem_bytes = sizeof(typename Attention::SharedStorage); if (smem_bytes > 0xc000) { cudaFuncSetAttribute(kernel_fn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes); } if (!Attention::check_supported(p)) { MNN_ERROR("Attention Kernel does not support these inputs\n"); return -1; } kernel_fn<<>>(p); return 0; } FmhaCommonExecution::FmhaCommonExecution(const MNN::Op* op, Backend* backend) : Execution(backend) { if(op->type() == OpType_FmhaV2) { auto fmha_v2_param = op->main_as_FmhaV2Param(); mNumHeads = fmha_v2_param->heads(); mType = 0; } else if(op->type() == OpType_Fmhca) { auto fmhca_param = op->main_as_FmhcaParam(); mNumHeads = fmhca_param->heads(); mType = 1; } } ErrorCode FmhaCommonExecution::onResize(const std::vector& inputs, const std::vector& outputs) { auto pool = static_cast(backend())->getBufferPool(); auto runtime = static_cast(backend())->getCUDARuntime(); auto input = inputs[0]; auto output = outputs[0]; MNN_ASSERT(output->dimensions() == 3); mBatchSize = output->length(0); mSeqLen = output->length(1); mHeadSizeV = outputs[0]->length(2)/mNumHeads; mHeadSize = mHeadSizeV; mSeqLenKV = mSeqLen; if(mType == 1) { mSeqLenKV = inputs[1]->length(1); mHeadSize = inputs[0]->length(2)/mNumHeads; } mSM = runtime->compute_capability(); MemChunk buffer_q; if(mType == 0) { buffer_q = pool->alloc(mBatchSize * mSeqLen * mHeadSize * mNumHeads * sizeof(half)); mQ_Buffer = (void*)((uint8_t*)buffer_q.first + buffer_q.second); } auto buffer_k = pool->alloc(mBatchSize * mSeqLenKV * mHeadSize * mNumHeads * sizeof(half)); mK_Buffer = (void*)((uint8_t*)buffer_k.first + buffer_k.second); auto buffer_v = pool->alloc(mBatchSize * mSeqLenKV * mHeadSizeV * mNumHeads * sizeof(half)); mV_Buffer = (void*)((uint8_t*)buffer_v.first + buffer_v.second); // output size auto buffer_acc = pool->alloc(mBatchSize * mSeqLen * mHeadSizeV * mNumHeads * sizeof(float)); mAcc_Buffer = (void*)((uint8_t*)buffer_acc.first + buffer_acc.second); if(mType == 0) { pool->free(buffer_q); } pool->free(buffer_k); pool->free(buffer_v); pool->free(buffer_acc); return NO_ERROR; } ErrorCode FmhaCommonExecution::onExecute(const std::vector& inputs, const std::vector& outputs) { #ifdef LOG_VERBOSE MNN_PRINT("start FmhaCommonExecution onExecute..."); #endif auto runtime = static_cast(backend())->getCUDARuntime(); size_t count = mBatchSize * mSeqLenKV * mHeadSizeV * mNumHeads; int block_num = runtime->blocks_num(count); int thread_num = runtime->threads_num(); //printf("type:%d, %p %p %p %p, %d %d %d %d %d\n", mType, inputs[0]->deviceId(), mQ_Buffer, mK_Buffer, mV_Buffer, mHeadSizeV, mNumHeads, mSeqLen, mSeqLenKV, mBatchSize); if(mType == 0) { SPLIT_FusedQKV<<>>(count, (const half*)inputs[0]->deviceId(), (half *)mQ_Buffer, (half *)mK_Buffer, (half *)mV_Buffer, mHeadSizeV); checkKernelErrors; } if(mType == 1) { mQ_Buffer = (void *)inputs[0]->deviceId(); SPLIT_FusedKV<<>>(count, (const half*)inputs[1]->deviceId(), (half *)mK_Buffer, (half *)mV_Buffer, mHeadSizeV); checkKernelErrors; } // Determine kernel configuration based on head size. // If head size is less than or equal to 64, each block operates over 64 queries and // 64 keys, and partial results can be stored in the register file. // If head size is greater than 64, each block operates over 32 queries and 128 keys, // and partial results are stored in shared memory. int ret = 0; if (mHeadSize > 64) { static int const kQueriesPerBlock = 32; static int const kKeysPerBlock = 128; if (mHeadSize <= 128) { ret = run_attention(inputs, outputs); } else { ret = run_attention(inputs, outputs); } } else { static constexpr int kMaxK = 64; // <- Decrease to 32/16 if your problem is smaller static int const kQueriesPerBlock = 64; static int const kKeysPerBlock = 64; ret = run_attention(inputs, outputs); } // printf("fmha shape b:%d s:%d %d h_num:%d h_size:%d, %d\n", mBatchSize, mSeqLen, mSeqLenKV, mNumHeads, mHeadSize, mHeadSizeV); checkKernelErrors; if(ret != 0) { MNN_ERROR("FmhaCommonExecution error\n"); } #ifdef LOG_VERBOSE MNN_PRINT("end FmhaCommonExecution onExecute..."); #endif return NO_ERROR; } } // namespace CUDA } // namespace MNN