#ifdef MNN_SUPPORT_TRANSFORMER_FUSE #include "VulkanAttention.hpp" #include "core/Macro.h" #include "core/TensorUtils.hpp" #include "backend/vulkan/vulkan/vulkan_wrapper.h" #include namespace MNN { static inline float _invSqrt(float x) { return 1.0f / ::sqrtf(x); } static uint32_t _selectSoftmaxLocalSize(int totalLen, uint32_t maxSizeX, uint32_t maxInvocations) { if (totalLen <= 1) { return 1; } uint32_t cap = 128; cap = ALIMIN(cap, maxSizeX); cap = ALIMIN(cap, maxInvocations); cap = ALIMIN(cap, (uint32_t)totalLen); uint32_t localSize = 1; while ((localSize << 1) <= cap) { localSize <<= 1; } return localSize; } static constexpr int kAttentionPrefillKBlock = 512; static bool _supportDecodeQ1Subgroup(const VulkanDevice& device) { const auto& subgroup = device.getSubgroupInfo(); if (0 == subgroup.size) { return false; } if (0 == (subgroup.stages & VK_SHADER_STAGE_COMPUTE_BIT)) { return false; } const VkSubgroupFeatureFlags required = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT; if ((subgroup.ops & required) != required) { return false; } return true; } void VulkanAttention::KVCache::reset() { maxLen = 0; kvHeadNum = 0; headDim = 0; fp16 = false; key = nullptr; value = nullptr; } void VulkanAttention::KVCache::ensureCapacity(VulkanBackend* vkBn, int requiredLen, int kvH, int d, bool useFP16) { MNN_ASSERT(requiredLen >= 0); MNN_ASSERT(kvH > 0); MNN_ASSERT(d > 0); if (kvHeadNum != kvH || headDim != d || fp16 != useFP16 || nullptr == key || nullptr == value) { reset(); kvHeadNum = kvH; headDim = d; fp16 = useFP16; maxLen = requiredLen + expandChunk; maxLen = ALIMAX(maxLen, expandChunk); const size_t bytes = fp16 ? sizeof(uint16_t) : sizeof(float); const size_t bufSize = (size_t)maxLen * (size_t)kvHeadNum * (size_t)headDim * bytes; key.reset(new VulkanBuffer(vkBn->getMemoryPool(), false, bufSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)); value.reset(new VulkanBuffer(vkBn->getMemoryPool(), false, bufSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)); return; } if (requiredLen <= maxLen) { return; } const int oldMaxLen = maxLen; maxLen = requiredLen + expandChunk; const size_t bytes = fp16 ? sizeof(uint16_t) : sizeof(float); const size_t newSize = (size_t)maxLen * (size_t)kvHeadNum * (size_t)headDim * bytes; std::shared_ptr newKey(new VulkanBuffer(vkBn->getMemoryPool(), false, newSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)); std::shared_ptr newValue(new VulkanBuffer(vkBn->getMemoryPool(), false, newSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)); // Preserve old content. // // cacheKey is packed as [kvHeadNum, headDim/4, maxLen, 4], so changing maxLen changes the row stride and we must repack. // cacheValue is kvh-major as [kvHeadNum, maxLen, headDim], so changing maxLen changes the kvh stride and we must repack too. const size_t oldSize = key->size(); if (oldSize > 0) { // Value: repack kvh blocks with new stride. { const VkDeviceSize rowBytes = (VkDeviceSize)oldMaxLen * (VkDeviceSize)headDim * (VkDeviceSize)bytes; const VkDeviceSize srcStride = rowBytes; const VkDeviceSize dstStride = (VkDeviceSize)maxLen * (VkDeviceSize)headDim * (VkDeviceSize)bytes; std::vector regions; regions.reserve((size_t)kvHeadNum); for (int kvh = 0; kvh < kvHeadNum; ++kvh) { VkBufferCopy c; c.srcOffset = (VkDeviceSize)kvh * srcStride; c.dstOffset = (VkDeviceSize)kvh * dstStride; c.size = rowBytes; regions.emplace_back(c); } vkBn->copyGPUToGPUBufferRegions(value->buffer(), newValue->buffer(), regions.data(), (uint32_t)regions.size()); } // Key: repack rows with new stride. const int d4Size = headDim / 4; MNN_ASSERT(d4Size > 0); const uint32_t rowCount = (uint32_t)kvHeadNum * (uint32_t)d4Size; const VkDeviceSize vec4Bytes = (VkDeviceSize)(4 * bytes); const VkDeviceSize srcRowStride = (VkDeviceSize)oldMaxLen * vec4Bytes; const VkDeviceSize dstRowStride = (VkDeviceSize)maxLen * vec4Bytes; std::vector regions; regions.reserve(rowCount); for (uint32_t r = 0; r < rowCount; ++r) { VkBufferCopy c; c.srcOffset = (VkDeviceSize)r * srcRowStride; c.dstOffset = (VkDeviceSize)r * dstRowStride; c.size = srcRowStride; regions.emplace_back(c); } vkBn->copyGPUToGPUBufferRegions(key->buffer(), newKey->buffer(), regions.data(), (uint32_t)regions.size()); } key = newKey; value = newValue; } VulkanAttention::VulkanAttention(const Op* op, Backend* bn) : VulkanBasicExecution(bn), mOp(op) { auto vkBn = static_cast(bn); mUseFP16 = vkBn->useFP16(); mMeta = reinterpret_cast(vkBn->getMetaPtr()); if (nullptr != op && nullptr != op->main_as_AttentionParam()) { mNeedKvCache = op->main_as_AttentionParam()->kv_cache(); } mKVCache.reset(new KVCache); mParam = vkBn->allocUniform(nullptr, sizeof(GpuParam)); if (!mNeedKvCache) { std::vector typesAttn{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // output VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // query VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // keyIn VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // valueIn VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheKey VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheValue VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // mask VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string attnName = "glsl_attention_fused_"; if (mUseFP16) { attnName += "FP16_"; } attnName += "comp"; mAttentionLegacyPipeline = vkBn->getPipeline(attnName, typesAttn); MNN_ASSERT(nullptr != mAttentionLegacyPipeline); mAttentionLegacySet.reset(mAttentionLegacyPipeline->createSet()); return; } // kv_cache=true path: pre-create update/prefill/decode pipelines to avoid resize cold-start. { std::vector typesUpdate{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // keyIn VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // valueIn VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheKey VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheValue VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string updateName = "glsl_attention_kvcache_update_"; if (mUseFP16) { updateName += "FP16_"; } updateName += "comp"; mUpdatePipeline = vkBn->getPipeline(updateName, typesUpdate); MNN_ASSERT(nullptr != mUpdatePipeline); mUpdateSet.reset(mUpdatePipeline->createSet()); } { std::vector typesRearrange{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // queryOut VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // queryIn VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string rqName = "glsl_attention_prefill_rearrange_q_"; if (mUseFP16) { rqName += "FP16_"; } rqName += "comp"; mRearrangeQPipeline = vkBn->getPipeline(rqName, typesRearrange); MNN_ASSERT(nullptr != mRearrangeQPipeline); mRearrangeQSet.reset(mRearrangeQPipeline->createSet()); } { std::vector typesInit{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // m VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // l VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // alpha VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // oAcc VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string initName = "glsl_attention_prefill_kblock_init_state_"; if (mUseFP16) { initName += "FP16_"; } initName += "comp"; mInitStatePipeline = vkBn->getPipeline(initName, typesInit); MNN_ASSERT(nullptr != mInitStatePipeline); mInitStateSet.reset(mInitStatePipeline->createSet()); } { std::vector typesQK{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // qk VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // query VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheKey VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // mask VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string qkName = "glsl_attention_prefill_kblock_qk_"; if (mUseFP16) { qkName += "FP16_"; } qkName += "comp"; mQKBlockPipeline = vkBn->getPipeline(qkName, typesQK); MNN_ASSERT(nullptr != mQKBlockPipeline); mQKBlockSet.reset(mQKBlockPipeline->createSet()); std::string qkFullName = "glsl_attention_prefill_kblock_qk_full_"; if (mUseFP16) { qkFullName += "FP16_"; } qkFullName += "comp"; mQKBlockFullPipeline = vkBn->getPipeline(qkFullName, typesQK); MNN_ASSERT(nullptr != mQKBlockFullPipeline); mQKBlockFullSet.reset(mQKBlockFullPipeline->createSet()); } { std::vector typesSoftmax{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // w VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // qk VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // m VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // l VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // alpha VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string softmaxName = "glsl_attention_prefill_kblock_softmax_online_"; if (mUseFP16) { softmaxName += "FP16_"; } softmaxName += "comp"; const auto& limits = vkBn->getDevice().proty().limits; const int kBlock4 = UP_DIV(kAttentionPrefillKBlock, 4) * 4; const int maxK4 = UP_DIV(kBlock4, 4); uint32_t localSize = _selectSoftmaxLocalSize(maxK4, (uint32_t)limits.maxComputeWorkGroupSize[0], (uint32_t)limits.maxComputeWorkGroupInvocations); mSoftmaxOnlinePipeline = vkBn->getPipeline(softmaxName, typesSoftmax, {localSize}); MNN_ASSERT(nullptr != mSoftmaxOnlinePipeline); mSoftmaxOnlineSet.reset(mSoftmaxOnlinePipeline->createSet()); mSoftmaxOnlineLocalSize = localSize; } { std::vector typesQKV{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // oAcc VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // w VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheValue VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // alpha VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string qkvName = "glsl_attention_prefill_kblock_qkv_acc_"; if (mUseFP16) { qkvName += "FP16_"; } qkvName += "comp"; mQKVAccPipeline = vkBn->getPipeline(qkvName, typesQKV); MNN_ASSERT(nullptr != mQKVAccPipeline); mQKVAccSet.reset(mQKVAccPipeline->createSet()); std::string qkvFullName = "glsl_attention_prefill_kblock_qkv_acc_full_"; if (mUseFP16) { qkvFullName += "FP16_"; } qkvFullName += "comp"; mQKVAccFullPipeline = vkBn->getPipeline(qkvFullName, typesQKV); MNN_ASSERT(nullptr != mQKVAccFullPipeline); mQKVAccFullSet.reset(mQKVAccFullPipeline->createSet()); } { std::vector typesFinal{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // output VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // oAcc VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // l VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string finalName = "glsl_attention_prefill_kblock_finalize_"; if (mUseFP16) { finalName += "FP16_"; } finalName += "comp"; mFinalizePipeline = vkBn->getPipeline(finalName, typesFinal); MNN_ASSERT(nullptr != mFinalizePipeline); mFinalizeSet.reset(mFinalizePipeline->createSet()); } { std::vector typesAttn{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // output VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // query VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // keyIn VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // valueIn VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheKey VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // cacheValue VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // mask VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER // param }; std::string attnName = "glsl_attention_fused_packed_"; if (mUseFP16) { attnName += "FP16_"; } attnName += "comp"; mAttentionPipeline = vkBn->getPipeline(attnName, typesAttn); MNN_ASSERT(nullptr != mAttentionPipeline); mAttentionSet.reset(mAttentionPipeline->createSet()); if (_supportDecodeQ1Subgroup(vkBn->getDevice())) { mDecodeQ1SubgroupLocalSize = vkBn->getDevice().getSubgroupSize(); if (mDecodeQ1SubgroupLocalSize > 0) { std::string decodeQ1Name = "glsl_attention_decode_q1_subgroup_"; if (mUseFP16) { decodeQ1Name += "FP16_"; } decodeQ1Name += "comp"; mDecodeQ1SubgroupPipeline = vkBn->getPipeline(decodeQ1Name, typesAttn, {mDecodeQ1SubgroupLocalSize}); if (nullptr != mDecodeQ1SubgroupPipeline) { mDecodeQ1SubgroupSet.reset(mDecodeQ1SubgroupPipeline->createSet()); } std::string decodeQ1HD128Name = "glsl_attention_decode_q1_subgroup_hd128_"; if (mUseFP16) { decodeQ1HD128Name += "FP16_"; } decodeQ1HD128Name += "comp"; mDecodeQ1SubgroupHD128Pipeline = vkBn->getPipeline(decodeQ1HD128Name, typesAttn, {mDecodeQ1SubgroupLocalSize}); if (nullptr != mDecodeQ1SubgroupHD128Pipeline) { mDecodeQ1SubgroupHD128Set.reset(mDecodeQ1SubgroupHD128Pipeline->createSet()); } } } } } VulkanAttention::~VulkanAttention() { auto vkBn = static_cast(backend()); if (mTempQuery) { vkBn->onReleaseBuffer(mTempQuery.get(), Backend::DYNAMIC); mTempQuery.reset(); } if (mTempQKBlock) { vkBn->onReleaseBuffer(mTempQKBlock.get(), Backend::DYNAMIC); mTempQKBlock.reset(); } if (mTempWBlock) { vkBn->onReleaseBuffer(mTempWBlock.get(), Backend::DYNAMIC); mTempWBlock.reset(); } if (mTempM) { vkBn->onReleaseBuffer(mTempM.get(), Backend::DYNAMIC); mTempM.reset(); } if (mTempL) { vkBn->onReleaseBuffer(mTempL.get(), Backend::DYNAMIC); mTempL.reset(); } if (mTempAlpha) { vkBn->onReleaseBuffer(mTempAlpha.get(), Backend::DYNAMIC); mTempAlpha.reset(); } if (mTempOAcc) { vkBn->onReleaseBuffer(mTempOAcc.get(), Backend::DYNAMIC); mTempOAcc.reset(); } vkBn->recycleUniform(mParam); } bool VulkanAttention::onClone(Backend* bn, const Op* op, VulkanBasicExecution** dst) { if (nullptr == dst) { return true; } auto res = new VulkanAttention(op, bn); res->mKVCache = mKVCache; res->mMeta = mMeta; *dst = res; return true; } ErrorCode VulkanAttention::onEncode(const std::vector& inputs, const std::vector& outputs, const VulkanCommandPool::Buffer* cmdBuffer) { MNN_ASSERT(!inputs.empty()); MNN_ASSERT(!outputs.empty()); auto query = inputs[0]; auto key = inputs[1]; auto value = inputs[2]; MNN_ASSERT(nullptr != query && nullptr != key && nullptr != value); MNN_ASSERT(query->dimensions() == 4); MNN_ASSERT(key->dimensions() == 4); MNN_ASSERT(value->dimensions() == 4); MNN_ASSERT(query->length(0) == 1); MNN_ASSERT(key->length(0) == 1); MNN_ASSERT(value->length(0) == 1); mQueryLen = query->length(1); mKeyLen = key->length(1); mHeadNum = query->length(2); mHeadDim = query->length(3); mKvHeadNum = key->length(2); MNN_ASSERT(mHeadNum > 0 && mKvHeadNum > 0); MNN_ASSERT(mHeadNum % mKvHeadNum == 0); MNN_ASSERT(mHeadDim > 0); MNN_ASSERT((mHeadDim & 3) == 0); MNN_ASSERT(mHeadDim <= 256); MNN_ASSERT(value->length(1) == mKeyLen); MNN_ASSERT(value->length(2) == mKvHeadNum); MNN_ASSERT(value->length(3) == mHeadDim); auto vkBn = static_cast(backend()); auto cmd = cmdBuffer->get(); #ifdef ENABLE_VULKAN_TIME_PROFILE auto dispatchWithProfile = [&](const char* name, const VulkanPipeline* pipeline, const std::shared_ptr& set, uint32_t x, uint32_t y, uint32_t z) { auto* profiler = vkBn->timeProfiler(); if (nullptr != profiler) { VulkanTimeProfileScope scope(profiler, cmd, name, VulkanTimeProfiler::Kind::Shader); pipeline->bind(cmd, set->get()); vkCmdDispatch(cmd, x, y, z); return; } pipeline->bind(cmd, set->get()); vkCmdDispatch(cmd, x, y, z); }; #else auto dispatchWithProfile = [&](const char*, const VulkanPipeline* pipeline, const std::shared_ptr& set, uint32_t x, uint32_t y, uint32_t z) { pipeline->bind(cmd, set->get()); vkCmdDispatch(cmd, x, y, z); }; #endif const bool usePrefill = mNeedKvCache && mQueryLen > 1; mUsePrefill = usePrefill; if (mNeedKvCache) { MNN_ASSERT(nullptr != mUpdatePipeline); MNN_ASSERT(nullptr != mUpdateSet); // Dispatch: KV update (x=dim/4, y=keyLen, z=kvHeadNum). dispatchWithProfile(mUseFP16 ? "glsl_attention_kvcache_update_FP16_comp" : "glsl_attention_kvcache_update_comp", mUpdatePipeline, mUpdateSet, UP_DIV(mHeadDim / 4, 8), mKeyLen, mKvHeadNum); // NOTE: KV cache buffers may be reallocated in onBeforeExecute (descriptor set updated there), so we must not // record a VkBufferMemoryBarrier with a stale VkBuffer handle here. Use a global memory barrier instead. { VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr); } } if (usePrefill) { constexpr int K_BLOCK = kAttentionPrefillKBlock; int pastLenForPrefill = 0; if (mNeedKvCache) { MNN_ASSERT(nullptr != mMeta); MNN_ASSERT(mMeta->n_reserve == 0); MNN_ASSERT(mMeta->computeReverseSize() == 0); const int previous = (int)mMeta->previous; const int remove = (int)mMeta->remove; MNN_ASSERT(previous >= 0); MNN_ASSERT(remove >= 0); MNN_ASSERT(remove <= previous); pastLenForPrefill = previous - remove; } mPrefillTotalLen = pastLenForPrefill + mKeyLen; mQueryLen4 = UP_DIV(mQueryLen, 4) * 4; MNN_ASSERT(mPrefillTotalLen > 0); const int64_t queryElementsI64 = (int64_t)mHeadNum * (int64_t)mHeadDim * (int64_t)mQueryLen4; MNN_ASSERT(queryElementsI64 > 0 && queryElementsI64 <= (int64_t)INT_MAX); const int queryElements = (int)queryElementsI64; const int kBlock4 = UP_DIV(K_BLOCK, 4) * 4; const int64_t rowCountI64 = (int64_t)mQueryLen * (int64_t)mHeadNum; MNN_ASSERT(rowCountI64 > 0 && rowCountI64 <= (int64_t)INT_MAX); const int rowCount = (int)rowCountI64; const int64_t qkElementsI64 = (int64_t)rowCount * (int64_t)kBlock4; MNN_ASSERT(qkElementsI64 > 0 && qkElementsI64 <= (int64_t)INT_MAX); const int qkElements = (int)qkElementsI64; const int64_t oaccElementsI64 = (int64_t)rowCount * (int64_t)mHeadDim; MNN_ASSERT(oaccElementsI64 > 0 && oaccElementsI64 <= (int64_t)INT_MAX); const int oaccElements = (int)oaccElementsI64; // Acquire workspace tensors fresh each onEncode (Backend::DYNAMIC). They are released at the end of this // call so other ops within the same resize can reuse the pool chunks. Descriptor sets capture // (VkBuffer, offset) below; the underlying GPU memory stays alive past release because the parent // VkBuffer is owned by the pool, and command-buffer order + barriers serialize cross-op access. // M / L / Alpha / OAcc must be FP32 even when the backend runs FP16 -> int tensor forces 4-byte storage. auto acquireTemp = [&](std::shared_ptr& t, Tensor* dev) -> bool { t.reset(dev); return vkBn->onAcquireBuffer(t.get(), Backend::DYNAMIC); }; if (!acquireTemp(mTempQuery, Tensor::createDevice({queryElements}))) return OUT_OF_MEMORY; if (!acquireTemp(mTempQKBlock, Tensor::createDevice({qkElements}))) return OUT_OF_MEMORY; if (!acquireTemp(mTempWBlock, Tensor::createDevice({qkElements}))) return OUT_OF_MEMORY; if (!acquireTemp(mTempM, Tensor::createDevice({rowCount}))) return OUT_OF_MEMORY; if (!acquireTemp(mTempL, Tensor::createDevice({rowCount}))) return OUT_OF_MEMORY; if (!acquireTemp(mTempAlpha, Tensor::createDevice({rowCount}))) return OUT_OF_MEMORY; if (!acquireTemp(mTempOAcc, Tensor::createDevice({oaccElements}))) return OUT_OF_MEMORY; MNN_ASSERT(nullptr != mRearrangeQPipeline); MNN_ASSERT(nullptr != mRearrangeQSet); MNN_ASSERT(nullptr != mInitStatePipeline); MNN_ASSERT(nullptr != mInitStateSet); MNN_ASSERT(nullptr != mQKBlockPipeline); MNN_ASSERT(nullptr != mQKBlockSet); MNN_ASSERT(nullptr != mQKBlockFullPipeline); MNN_ASSERT(nullptr != mQKBlockFullSet); MNN_ASSERT(nullptr != mSoftmaxOnlinePipeline); MNN_ASSERT(nullptr != mSoftmaxOnlineSet); MNN_ASSERT(nullptr != mQKVAccPipeline); MNN_ASSERT(nullptr != mQKVAccSet); MNN_ASSERT(nullptr != mQKVAccFullPipeline); MNN_ASSERT(nullptr != mQKVAccFullSet); MNN_ASSERT(nullptr != mFinalizePipeline); MNN_ASSERT(nullptr != mFinalizeSet); // Bind workspace + uniform descriptor slots here (their (VkBuffer, offset) is stable across executes). // Cache (cacheKey/cacheValue) and external I/O (query/mask/output) are bound in onBeforeExecute because // KV cache may be reallocated by ensureCapacity(). auto tqBuf = vkBn->getTensorBuffer(mTempQuery.get()); auto qkBuf = vkBn->getTensorBuffer(mTempQKBlock.get()); auto wBuf = vkBn->getTensorBuffer(mTempWBlock.get()); auto mBuf = vkBn->getTensorBuffer(mTempM.get()); auto lBuf = vkBn->getTensorBuffer(mTempL.get()); auto aBuf = vkBn->getTensorBuffer(mTempAlpha.get()); auto oBuf = vkBn->getTensorBuffer(mTempOAcc.get()); mRearrangeQSet->writeBuffer(tqBuf.first->buffer(), 0, vkBn->getTensorSize(mTempQuery.get()), tqBuf.second); mRearrangeQSet->writeBuffer(mParam->buffer(), 2, mParam->size()); mInitStateSet->writeBuffer(mBuf.first->buffer(), 0, vkBn->getTensorSize(mTempM.get()), mBuf.second); mInitStateSet->writeBuffer(lBuf.first->buffer(), 1, vkBn->getTensorSize(mTempL.get()), lBuf.second); mInitStateSet->writeBuffer(aBuf.first->buffer(), 2, vkBn->getTensorSize(mTempAlpha.get()), aBuf.second); mInitStateSet->writeBuffer(oBuf.first->buffer(), 3, vkBn->getTensorSize(mTempOAcc.get()), oBuf.second); mInitStateSet->writeBuffer(mParam->buffer(), 4, mParam->size()); mQKBlockSet->writeBuffer(qkBuf.first->buffer(), 0, vkBn->getTensorSize(mTempQKBlock.get()), qkBuf.second); mQKBlockSet->writeBuffer(tqBuf.first->buffer(), 1, vkBn->getTensorSize(mTempQuery.get()), tqBuf.second); mQKBlockSet->writeBuffer(mParam->buffer(), 4, mParam->size()); mQKBlockFullSet->writeBuffer(qkBuf.first->buffer(), 0, vkBn->getTensorSize(mTempQKBlock.get()), qkBuf.second); mQKBlockFullSet->writeBuffer(tqBuf.first->buffer(), 1, vkBn->getTensorSize(mTempQuery.get()), tqBuf.second); mQKBlockFullSet->writeBuffer(mParam->buffer(), 4, mParam->size()); mSoftmaxOnlineSet->writeBuffer(wBuf.first->buffer(), 0, vkBn->getTensorSize(mTempWBlock.get()), wBuf.second); mSoftmaxOnlineSet->writeBuffer(qkBuf.first->buffer(), 1, vkBn->getTensorSize(mTempQKBlock.get()), qkBuf.second); mSoftmaxOnlineSet->writeBuffer(mBuf.first->buffer(), 2, vkBn->getTensorSize(mTempM.get()), mBuf.second); mSoftmaxOnlineSet->writeBuffer(lBuf.first->buffer(), 3, vkBn->getTensorSize(mTempL.get()), lBuf.second); mSoftmaxOnlineSet->writeBuffer(aBuf.first->buffer(), 4, vkBn->getTensorSize(mTempAlpha.get()), aBuf.second); mSoftmaxOnlineSet->writeBuffer(mParam->buffer(), 5, mParam->size()); mQKVAccSet->writeBuffer(oBuf.first->buffer(), 0, vkBn->getTensorSize(mTempOAcc.get()), oBuf.second); mQKVAccSet->writeBuffer(wBuf.first->buffer(), 1, vkBn->getTensorSize(mTempWBlock.get()), wBuf.second); mQKVAccSet->writeBuffer(aBuf.first->buffer(), 3, vkBn->getTensorSize(mTempAlpha.get()), aBuf.second); mQKVAccSet->writeBuffer(mParam->buffer(), 4, mParam->size()); mQKVAccFullSet->writeBuffer(oBuf.first->buffer(), 0, vkBn->getTensorSize(mTempOAcc.get()), oBuf.second); mQKVAccFullSet->writeBuffer(wBuf.first->buffer(), 1, vkBn->getTensorSize(mTempWBlock.get()), wBuf.second); mQKVAccFullSet->writeBuffer(aBuf.first->buffer(), 3, vkBn->getTensorSize(mTempAlpha.get()), aBuf.second); mQKVAccFullSet->writeBuffer(mParam->buffer(), 4, mParam->size()); mFinalizeSet->writeBuffer(oBuf.first->buffer(), 1, vkBn->getTensorSize(mTempOAcc.get()), oBuf.second); mFinalizeSet->writeBuffer(lBuf.first->buffer(), 2, vkBn->getTensorSize(mTempL.get()), lBuf.second); mFinalizeSet->writeBuffer(mParam->buffer(), 3, mParam->size()); // 1) Rearrange Q to packed-D Qtmp: (x=qLen4, y=headDim/4, z=headNum) dispatchWithProfile(mUseFP16 ? "glsl_attention_prefill_rearrange_q_FP16_comp" : "glsl_attention_prefill_rearrange_q_comp", mRearrangeQPipeline, mRearrangeQSet, UP_DIV(mQueryLen4, 8), UP_DIV(mHeadDim / 4, 8), mHeadNum); cmdBuffer->barrierSource(tqBuf.first->buffer(), tqBuf.second, vkBn->getTensorSize(mTempQuery.get())); // K-block prefill: online softmax in K dimension to avoid O(qLen*totalLen) intermediates. dispatchWithProfile(mUseFP16 ? "glsl_attention_prefill_kblock_init_state_FP16_comp" : "glsl_attention_prefill_kblock_init_state_comp", mInitStatePipeline, mInitStateSet, UP_DIV((uint32_t)mQueryLen * (uint32_t)mHeadNum * (uint32_t)mHeadDim, 256), 1, 1); cmdBuffer->barrierSource(mBuf.first->buffer(), mBuf.second, vkBn->getTensorSize(mTempM.get())); cmdBuffer->barrierSource(lBuf.first->buffer(), lBuf.second, vkBn->getTensorSize(mTempL.get())); cmdBuffer->barrierSource(aBuf.first->buffer(), aBuf.second, vkBn->getTensorSize(mTempAlpha.get())); cmdBuffer->barrierSource(oBuf.first->buffer(), oBuf.second, vkBn->getTensorSize(mTempOAcc.get())); struct QKPushConst { uint32_t kStart; uint32_t blockLen; }; struct SoftmaxPushConst { uint32_t blockLen; }; auto dispatchWithPushConst = [&](const char* name, const VulkanPipeline* pipeline, const std::shared_ptr& set, uint32_t x, uint32_t y, uint32_t z, const void* pcData, uint32_t pcSize) { #ifdef ENABLE_VULKAN_TIME_PROFILE auto* profiler = vkBn->timeProfiler(); if (nullptr != profiler) { VulkanTimeProfileScope scope(profiler, cmd, name, VulkanTimeProfiler::Kind::Shader); pipeline->bind(cmd, set->get()); vkCmdPushConstants(cmd, pipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, pcSize, pcData); vkCmdDispatch(cmd, x, y, z); return; } #endif pipeline->bind(cmd, set->get()); vkCmdPushConstants(cmd, pipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, pcSize, pcData); vkCmdDispatch(cmd, x, y, z); }; const int totalLen = mPrefillTotalLen; const int kBlock = K_BLOCK; for (int kStart = 0; kStart < totalLen; kStart += kBlock) { const int blockLen = ALIMIN(kBlock, totalLen - kStart); const int blockLen4 = UP_DIV(blockLen, 4) * 4; const int blockLen4_4 = UP_DIV(blockLen4, 4); // 2) QK block: (x=blockLen4/4, y=qLen4/4, z=headNum) QKPushConst pcQK{(uint32_t)kStart, (uint32_t)blockLen}; const bool fullBlock = (blockLen == kBlock) && (kStart + kBlock <= totalLen); const VulkanPipeline* qkPipe = fullBlock ? mQKBlockFullPipeline : mQKBlockPipeline; const std::shared_ptr& qkSet = fullBlock ? mQKBlockFullSet : mQKBlockSet; const char* qkName = nullptr; if (fullBlock) { qkName = mUseFP16 ? "glsl_attention_prefill_kblock_qk_full_FP16_comp" : "glsl_attention_prefill_kblock_qk_full_comp"; } else { qkName = mUseFP16 ? "glsl_attention_prefill_kblock_qk_FP16_comp" : "glsl_attention_prefill_kblock_qk_comp"; } dispatchWithPushConst(qkName, qkPipe, qkSet, UP_DIV((uint32_t)blockLen4_4, 8), UP_DIV((uint32_t)UP_DIV(mQueryLen4, 4), 8), (uint32_t)mHeadNum, &pcQK, sizeof(pcQK)); cmdBuffer->barrierSource(qkBuf.first->buffer(), qkBuf.second, vkBn->getTensorSize(mTempQKBlock.get())); // 3) Softmax online: updates m/l and writes unnormalized w (x=headNum, y=qLen) SoftmaxPushConst pcSM{(uint32_t)blockLen}; dispatchWithPushConst(mUseFP16 ? "glsl_attention_prefill_kblock_softmax_online_FP16_comp" : "glsl_attention_prefill_kblock_softmax_online_comp", mSoftmaxOnlinePipeline, mSoftmaxOnlineSet, (uint32_t)mHeadNum, (uint32_t)mQueryLen, 1, &pcSM, sizeof(pcSM)); cmdBuffer->barrierSource(wBuf.first->buffer(), wBuf.second, vkBn->getTensorSize(mTempWBlock.get())); cmdBuffer->barrierSource(mBuf.first->buffer(), mBuf.second, vkBn->getTensorSize(mTempM.get())); cmdBuffer->barrierSource(lBuf.first->buffer(), lBuf.second, vkBn->getTensorSize(mTempL.get())); cmdBuffer->barrierSource(aBuf.first->buffer(), aBuf.second, vkBn->getTensorSize(mTempAlpha.get())); // 4) QKV accumulate: (x=headDim/4, y=qLen/2, z=headNum) const VulkanPipeline* qkvPipe = fullBlock ? mQKVAccFullPipeline : mQKVAccPipeline; const std::shared_ptr& qkvSet = fullBlock ? mQKVAccFullSet : mQKVAccSet; const char* qkvName = nullptr; if (fullBlock) { qkvName = mUseFP16 ? "glsl_attention_prefill_kblock_qkv_acc_full_FP16_comp" : "glsl_attention_prefill_kblock_qkv_acc_full_comp"; } else { qkvName = mUseFP16 ? "glsl_attention_prefill_kblock_qkv_acc_FP16_comp" : "glsl_attention_prefill_kblock_qkv_acc_comp"; } dispatchWithPushConst(qkvName, qkvPipe, qkvSet, UP_DIV((uint32_t)(mHeadDim / 4), 8), UP_DIV((uint32_t)UP_DIV(mQueryLen, 2), 8), (uint32_t)mHeadNum, &pcQK, sizeof(pcQK)); cmdBuffer->barrierSource(oBuf.first->buffer(), oBuf.second, vkBn->getTensorSize(mTempOAcc.get())); } // 5) Finalize: output = oAcc / l dispatchWithProfile(mUseFP16 ? "glsl_attention_prefill_kblock_finalize_FP16_comp" : "glsl_attention_prefill_kblock_finalize_comp", mFinalizePipeline, mFinalizeSet, UP_DIV((uint32_t)(mHeadDim / 4), 8), UP_DIV((uint32_t)UP_DIV(mQueryLen, 2), 8), (uint32_t)mHeadNum); auto releaseTemp = [&](std::shared_ptr& t) { if (t) { vkBn->onReleaseBuffer(t.get(), Backend::DYNAMIC); t.reset(); } }; releaseTemp(mTempQuery); releaseTemp(mTempQKBlock); releaseTemp(mTempWBlock); releaseTemp(mTempM); releaseTemp(mTempL); releaseTemp(mTempAlpha); releaseTemp(mTempOAcc); return NO_ERROR; } // Decode (or kv_cache disabled): keep fused shader. mQueryLen4 = 0; if (mTempQuery) { vkBn->onReleaseBuffer(mTempQuery.get(), Backend::DYNAMIC); mTempQuery.reset(); } if (mTempQKBlock) { vkBn->onReleaseBuffer(mTempQKBlock.get(), Backend::DYNAMIC); mTempQKBlock.reset(); } if (mTempWBlock) { vkBn->onReleaseBuffer(mTempWBlock.get(), Backend::DYNAMIC); mTempWBlock.reset(); } if (mTempM) { vkBn->onReleaseBuffer(mTempM.get(), Backend::DYNAMIC); mTempM.reset(); } if (mTempL) { vkBn->onReleaseBuffer(mTempL.get(), Backend::DYNAMIC); mTempL.reset(); } if (mTempAlpha) { vkBn->onReleaseBuffer(mTempAlpha.get(), Backend::DYNAMIC); mTempAlpha.reset(); } if (mTempOAcc) { vkBn->onReleaseBuffer(mTempOAcc.get(), Backend::DYNAMIC); mTempOAcc.reset(); } mPrefillTotalLen = 0; if (mNeedKvCache) { const bool useDecodeQ1Subgroup = (mQueryLen == 1) && (nullptr != mDecodeQ1SubgroupPipeline) && (nullptr != mDecodeQ1SubgroupSet); if (useDecodeQ1Subgroup) { const bool useHD128 = (mHeadDim == 128) && (nullptr != mDecodeQ1SubgroupHD128Pipeline) && (nullptr != mDecodeQ1SubgroupHD128Set); if (useHD128) { dispatchWithProfile(mUseFP16 ? "glsl_attention_decode_q1_subgroup_hd128_FP16_comp" : "glsl_attention_decode_q1_subgroup_hd128_comp", mDecodeQ1SubgroupHD128Pipeline, mDecodeQ1SubgroupHD128Set, (uint32_t)mHeadNum, 1, 1); } else { dispatchWithProfile(mUseFP16 ? "glsl_attention_decode_q1_subgroup_FP16_comp" : "glsl_attention_decode_q1_subgroup_comp", mDecodeQ1SubgroupPipeline, mDecodeQ1SubgroupSet, (uint32_t)mHeadNum, 1, 1); } } else { MNN_ASSERT(nullptr != mAttentionPipeline); MNN_ASSERT(nullptr != mAttentionSet); dispatchWithProfile(mUseFP16 ? "glsl_attention_fused_packed_FP16_comp" : "glsl_attention_fused_packed_comp", mAttentionPipeline, mAttentionSet, UP_DIV(mHeadNum, 8), UP_DIV(mQueryLen, 8), 1); } } else { MNN_ASSERT(nullptr != mAttentionLegacyPipeline); MNN_ASSERT(nullptr != mAttentionLegacySet); dispatchWithProfile(mUseFP16 ? "glsl_attention_fused_FP16_comp" : "glsl_attention_fused_comp", mAttentionLegacyPipeline, mAttentionLegacySet, UP_DIV(mHeadNum, 8), UP_DIV(mQueryLen, 8), 1); } return NO_ERROR; } ErrorCode VulkanAttention::onBeforeExecute(const std::vector& inputs, const std::vector& outputs) { MNN_ASSERT(!inputs.empty()); MNN_ASSERT(!outputs.empty()); auto query = inputs[0]; auto key = inputs[1]; auto value = inputs[2]; auto output = outputs[0]; MNN_ASSERT(nullptr != query && nullptr != key && nullptr != value && nullptr != output); MNN_ASSERT(query->length(1) == mQueryLen); MNN_ASSERT(key->length(1) == mKeyLen); MNN_ASSERT(query->length(2) == mHeadNum); MNN_ASSERT(key->length(2) == mKvHeadNum); MNN_ASSERT(query->length(3) == mHeadDim); MNN_ASSERT(key->length(3) == mHeadDim); MNN_ASSERT(value->length(1) == mKeyLen); MNN_ASSERT(value->length(2) == mKvHeadNum); MNN_ASSERT(value->length(3) == mHeadDim); MNN_ASSERT(query->length(0) == 1); auto vkBn = static_cast(backend()); int pastLenForCompute = 0; if (mNeedKvCache) { MNN_ASSERT(nullptr != mMeta); MNN_ASSERT(mMeta->n_reserve == 0); MNN_ASSERT(mMeta->computeReverseSize() == 0); const int previous = (int)mMeta->previous; const int remove = (int)mMeta->remove; const int add = (int)mMeta->add; MNN_ASSERT(previous >= 0); MNN_ASSERT(remove >= 0); MNN_ASSERT(add >= 0); MNN_ASSERT(add <= mKeyLen); MNN_ASSERT(remove <= previous); pastLenForCompute = previous - remove; // Ensure capacity for compute window (pastLen + keyLen), because shaders read only from KV cache. mKVCache->ensureCapacity(vkBn, pastLenForCompute + mKeyLen, mKvHeadNum, mHeadDim, mUseFP16); } const int group = mHeadNum / mKvHeadNum; const int totalLenForCompute = pastLenForCompute + mKeyLen; int maskMode = 0; int maskQlen = 0; int maskKvlen = 0; const Tensor* mask = nullptr; if (inputs.size() > 3 && nullptr != inputs[3]) { mask = inputs[3]; MNN_ASSERT(mask->getType() == halide_type_of()); if (mask->shape().empty()) { // Match the transformer-fuse attention convention used by the tests: // a shape-empty scalar mask is the lower-triangular causal sentinel. maskMode = 2; } else { const int md = mask->dimensions(); MNN_ASSERT(md >= 2); maskQlen = mask->length(md - 2); maskKvlen = mask->length(md - 1); MNN_ASSERT(maskQlen == mQueryLen); MNN_ASSERT(maskKvlen > 0); maskMode = 1; } } auto gpuParam = reinterpret_cast(mParam->map()); gpuParam->s0[0] = mQueryLen; gpuParam->s0[1] = mKeyLen; gpuParam->s0[2] = mHeadNum; gpuParam->s0[3] = mKvHeadNum; gpuParam->s1[0] = mHeadDim; gpuParam->s1[1] = group; gpuParam->s1[2] = pastLenForCompute; gpuParam->s1[3] = totalLenForCompute; gpuParam->s2[0] = maskQlen; gpuParam->s2[1] = maskKvlen; gpuParam->s2[2] = maskMode; gpuParam->s2[3] = mNeedKvCache ? mKVCache->maxLen : 0; gpuParam->f0[0] = _invSqrt((float)mHeadDim); gpuParam->f0[1] = 0.0f; gpuParam->f0[2] = 0.0f; gpuParam->f0[3] = 0.0f; mParam->unmap(); // Bind buffers (update + attention). Note: when hasMask == 0, bind query buffer as placeholder. auto queryBuf = vkBn->getTensorBuffer(query); auto keyBuf = vkBn->getTensorBuffer(key); auto valueBuf = vkBn->getTensorBuffer(value); auto outBuf = vkBn->getTensorBuffer(output); const VkDeviceSize queryOffset = queryBuf.second; const VulkanBuffer* cacheKeyBuf = nullptr; const VulkanBuffer* cacheValueBuf = nullptr; VkDeviceSize cacheKeyOffset = 0; VkDeviceSize cacheValueOffset = 0; size_t cacheKeySize = 0; size_t cacheValueSize = 0; if (mNeedKvCache) { cacheKeyBuf = mKVCache->key.get(); cacheValueBuf = mKVCache->value.get(); MNN_ASSERT(nullptr != cacheKeyBuf && nullptr != cacheValueBuf); cacheKeySize = cacheKeyBuf->size(); cacheValueSize = cacheValueBuf->size(); } else { // KV cache disabled: alias cache buffers to current K/V (shaders read only from cache bindings). cacheKeyBuf = keyBuf.first; cacheValueBuf = valueBuf.first; cacheKeyOffset = keyBuf.second; cacheValueOffset = valueBuf.second; cacheKeySize = vkBn->getTensorSize(key); cacheValueSize = vkBn->getTensorSize(value); } // Update set (only when KV cache is enabled; kv_cache=false uses legacy fused shader directly on input K/V). if (mNeedKvCache) { mUpdateSet->writeBuffer(keyBuf.first->buffer(), 0, vkBn->getTensorSize(key), keyBuf.second); mUpdateSet->writeBuffer(valueBuf.first->buffer(), 1, vkBn->getTensorSize(value), valueBuf.second); mUpdateSet->writeBuffer(cacheKeyBuf->buffer(), 2, cacheKeySize, cacheKeyOffset); mUpdateSet->writeBuffer(cacheValueBuf->buffer(), 3, cacheValueSize, cacheValueOffset); mUpdateSet->writeBuffer(mParam->buffer(), 4, mParam->size()); } if (mUsePrefill) { MNN_ASSERT(totalLenForCompute == mPrefillTotalLen); MNN_ASSERT(mQueryLen4 == UP_DIV(mQueryLen, 4) * 4); // Only the bindings that may change between executes are rewritten below. MNN_ASSERT(nullptr != mRearrangeQSet); mRearrangeQSet->writeBuffer(queryBuf.first->buffer(), 1, vkBn->getTensorSize(query), queryBuf.second); mQKBlockSet->writeBuffer(cacheKeyBuf->buffer(), 2, cacheKeySize, cacheKeyOffset); mQKBlockFullSet->writeBuffer(cacheKeyBuf->buffer(), 2, cacheKeySize, cacheKeyOffset); if (maskMode == 1) { auto maskBuf = vkBn->getTensorBuffer(mask); mQKBlockSet->writeBuffer(maskBuf.first->buffer(), 3, vkBn->getTensorSize(mask), maskBuf.second); mQKBlockFullSet->writeBuffer(maskBuf.first->buffer(), 3, vkBn->getTensorSize(mask), maskBuf.second); } else { mQKBlockSet->writeBuffer(queryBuf.first->buffer(), 3, vkBn->getTensorSize(query), queryBuf.second); mQKBlockFullSet->writeBuffer(queryBuf.first->buffer(), 3, vkBn->getTensorSize(query), queryBuf.second); } mQKVAccSet->writeBuffer(cacheValueBuf->buffer(), 2, cacheValueSize, cacheValueOffset); mQKVAccFullSet->writeBuffer(cacheValueBuf->buffer(), 2, cacheValueSize, cacheValueOffset); mFinalizeSet->writeBuffer(outBuf.first->buffer(), 0, vkBn->getTensorSize(output), outBuf.second); return NO_ERROR; } // Attention set (fused). Keep packed fused set for fallback even when decode-q1 subgroup is available. auto writeAttentionSet = [&](const std::shared_ptr& set) { MNN_ASSERT(nullptr != set); set->writeBuffer(outBuf.first->buffer(), 0, vkBn->getTensorSize(output), outBuf.second); set->writeBuffer(queryBuf.first->buffer(), 1, vkBn->getTensorSize(query), queryBuf.second); set->writeBuffer(keyBuf.first->buffer(), 2, vkBn->getTensorSize(key), keyBuf.second); set->writeBuffer(valueBuf.first->buffer(), 3, vkBn->getTensorSize(value), valueBuf.second); set->writeBuffer(cacheKeyBuf->buffer(), 4, cacheKeySize, cacheKeyOffset); set->writeBuffer(cacheValueBuf->buffer(), 5, cacheValueSize, cacheValueOffset); if (maskMode == 1) { auto maskBuf = vkBn->getTensorBuffer(mask); set->writeBuffer(maskBuf.first->buffer(), 6, vkBn->getTensorSize(mask), maskBuf.second); } else { set->writeBuffer(queryBuf.first->buffer(), 6, vkBn->getTensorSize(query), queryBuf.second); } set->writeBuffer(mParam->buffer(), 7, mParam->size()); }; if (mNeedKvCache) { MNN_ASSERT(nullptr != mAttentionSet); writeAttentionSet(mAttentionSet); if (mQueryLen == 1 && nullptr != mDecodeQ1SubgroupSet) { writeAttentionSet(mDecodeQ1SubgroupSet); } if (mQueryLen == 1 && nullptr != mDecodeQ1SubgroupHD128Set) { writeAttentionSet(mDecodeQ1SubgroupHD128Set); } } else { MNN_ASSERT(nullptr != mAttentionLegacySet); writeAttentionSet(mAttentionLegacySet); } return NO_ERROR; } class VulkanAttentionCreator : public VulkanBackend::Creator { public: VulkanBasicExecution* onCreate(const std::vector& inputs, const std::vector& outputs, const MNN::Op* op, Backend* backend) const override { return new VulkanAttention(op, backend); } }; static bool gResistor = []() { VulkanBackend::addCreator(OpType_Attention, new VulkanAttentionCreator); return true; }(); } // namespace MNN #endif // MNN_SUPPORT_TRANSFORMER_FUSE