// // LinearAttentionBufExecution.cpp // MNN // // Created by MNN on 2026/02/12. // Copyright © 2018, Alibaba Group Holding Limited // #ifdef MNN_SUPPORT_TRANSFORMER_FUSE #include "LinearAttentionBufExecution.hpp" #include "core/TensorUtils.hpp" #include "core/OpCommonUtils.hpp" namespace MNN { namespace OpenCL { LinearAttentionBufExecution::LinearAttentionBufExecution(const MNN::Op *op, Backend *backend) : CommonExecution(backend, op) { mOpenCLBackend = static_cast(backend); mMeta = (KVMeta*)(backend->getMetaPtr()); auto param = op->main_as_LinearAttentionParam(); mAttentionType = param->attn_type()->str(); mNumKHeads = param->num_k_heads(); mNumVHeads = param->num_v_heads(); mHeadKDim = param->head_k_dim(); mHeadVDim = param->head_v_dim(); mUseQKL2Norm = param->use_qk_l2norm(); mStateCache.reset(new OpenCLStateCache); } ErrorCode LinearAttentionBufExecution::onResize(const std::vector &inputs, const std::vector &outputs) { auto qkv = inputs[0]; int batch = qkv->length(0); int convDim = qkv->length(1); int seqLen = qkv->length(2); // ─── Chunked prefill: fully independent branch ─── mUseChunkedPrefill = (seqLen > 1); if (mUseChunkedPrefill) { return onResizeChunkedPrefill(inputs, outputs); } int H = mNumVHeads; int dk = mHeadKDim; int dv = mHeadVDim; int K_conv = inputs[3]->length(2); int convStateSize = K_conv - 1; int key_dim = mNumKHeads * dk; int val_dim = mNumVHeads * dv; int gqa_factor = (mNumVHeads > mNumKHeads) ? (mNumVHeads / mNumKHeads) : 1; float qScale = 1.0f / sqrt((float)dk); auto runtime = mOpenCLBackend->getOpenCLRuntime(); // ─── Persistent state buffers (STATIC): allocate once, shared via onClone ─── int bytesPerElement = mOpenCLBackend->fpBytes(); if (mStateCache->mRecurrentState.get() == nullptr) { // First time: allocate and zero-initialize int rnnSize = batch * H * dk * dv; mStateCache->mRecurrentState.reset(Tensor::createDevice({rnnSize})); bool success = backend()->onAcquireBuffer(mStateCache->mRecurrentState.get(), Backend::STATIC); if (!success) return OUT_OF_MEMORY; { cl_int res; int bufferBytes = rnnSize * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mRecurrentState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mRecurrentState.get()), mapPtr); } } if (convStateSize > 0) { int convStateTotal = batch * convDim * convStateSize; mStateCache->mConvState.reset(Tensor::createDevice({convStateTotal})); success &= backend()->onAcquireBuffer(mStateCache->mConvState.get(), Backend::STATIC); if (!success) return OUT_OF_MEMORY; cl_int res; int bufferBytes = convStateTotal * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mConvState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mConvState.get()), mapPtr); } } } else if (seqLen > 1) { // Prefill: reset state for new sequence, UNLESS: // 1. Loading from prefix cache (PendingRead), or // 2. Reusing KV from previous inference (reuse_kv=true, i.e. previous != remove) bool loadingFromDisk = (mMeta != nullptr && mMeta->file_flag == KVMeta::PendingRead && mMeta->file_name.size() > 0); bool reusingKV = (mMeta != nullptr && mMeta->previous != mMeta->remove); if (!loadingFromDisk && !reusingKV) { { cl_int res; int bufferBytes = mStateCache->mRecurrentState->elementSize() * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mRecurrentState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mRecurrentState.get()), mapPtr); } } if (mStateCache->mConvState.get() != nullptr) { cl_int res; int bufferBytes = mStateCache->mConvState->elementSize() * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mConvState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mConvState.get()), mapPtr); } } } } // Decode (seqLen == 1): keep existing state untouched // Allocate temporary conv output buffer mConvOut.reset(Tensor::createDevice({batch * convDim * seqLen})); mOpenCLBackend->onAcquireBuffer(mConvOut.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mConvOut.get(), Backend::DYNAMIC); // Build kernels std::set buildOptions; int local_size = 16; buildOptions.emplace("-DLOCAL_SIZE=" + std::to_string(local_size)); buildOptions.emplace("-DK_SIZE=" + std::to_string(dv)); // Kernel 1: Conv1D + SiLU mKernelConvSilu = runtime->buildKernel("linear_attention_buf", "linear_attn_conv_silu", buildOptions, mOpenCLBackend->getPrecision()); int totalConvSilu = batch * convDim * seqLen; mGWSConvSilu = {(uint32_t)totalConvSilu, 1, 1}; auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelConvSilu)); uint32_t lwsConv = std::min(maxWorkGroupSize, (uint32_t)256); lwsConv = std::min(lwsConv, (uint32_t)totalConvSilu); mLWSConvSilu = {lwsConv, 1, 1}; // Kernel 2: Conv state update if (convStateSize > 0) { mKernelConvStateUpdate = runtime->buildKernel("linear_attention_buf", "linear_attn_conv_state_update", buildOptions, mOpenCLBackend->getPrecision()); int totalConvUpdate = batch * convDim * convStateSize; mGWSConvStateUpdate = {(uint32_t)totalConvUpdate, 1, 1}; maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelConvStateUpdate)); uint32_t lwsUpdate = std::min(maxWorkGroupSize, (uint32_t)256); lwsUpdate = std::min(lwsUpdate, (uint32_t)totalConvUpdate); mLWSConvStateUpdate = {lwsUpdate, 1, 1}; } // Kernel 3: Gated Delta Rule auto gateDeltaRuleBuildOptions = buildOptions; if(seqLen == 1){ gateDeltaRuleBuildOptions.emplace("-DDECODE_PHASE"); } mKernelGatedDeltaRule = runtime->buildKernel("linear_attention_buf", "linear_attn_gated_delta_rule", gateDeltaRuleBuildOptions, mOpenCLBackend->getPrecision()); // Set kernel arguments // Kernel 1: conv_silu { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelConvSilu->get().setArg(idx++, totalConvSilu); ret |= mKernelConvSilu->get().setArg(idx++, openCLBuffer(inputs[0])); // qkv ret |= mKernelConvSilu->get().setArg(idx++, openCLBuffer(mStateCache->mConvState.get())); // conv_state ret |= mKernelConvSilu->get().setArg(idx++, openCLBuffer(inputs[3])); // conv_weight ret |= mKernelConvSilu->get().setArg(idx++, openCLBuffer(mConvOut.get())); // conv_out ret |= mKernelConvSilu->get().setArg(idx++, batch); ret |= mKernelConvSilu->get().setArg(idx++, convDim); ret |= mKernelConvSilu->get().setArg(idx++, seqLen); ret |= mKernelConvSilu->get().setArg(idx++, K_conv); ret |= mKernelConvSilu->get().setArg(idx++, convStateSize); MNN_CHECK_CL_SUCCESS(ret, "setArg linear_attn_conv_silu"); } // Kernel 2: conv_state_update if (convStateSize > 0) { int totalConvUpdate = batch * convDim * convStateSize; uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelConvStateUpdate->get().setArg(idx++, totalConvUpdate); ret |= mKernelConvStateUpdate->get().setArg(idx++, openCLBuffer(inputs[0])); // qkv ret |= mKernelConvStateUpdate->get().setArg(idx++, openCLBuffer(mStateCache->mConvState.get())); // conv_state ret |= mKernelConvStateUpdate->get().setArg(idx++, batch); ret |= mKernelConvStateUpdate->get().setArg(idx++, convDim); ret |= mKernelConvStateUpdate->get().setArg(idx++, seqLen); ret |= mKernelConvStateUpdate->get().setArg(idx++, convStateSize); MNN_CHECK_CL_SUCCESS(ret, "setArg linear_attn_conv_state_update"); } // Kernel 2.5: l2 if(mUseQKL2Norm){ auto l2BuildOptions = buildOptions; if(seqLen > 1){ l2BuildOptions.emplace("-DUSE_VEC"); } mKernell2Norm = runtime->buildKernel("linear_attention_buf", "l2_norm", l2BuildOptions, mOpenCLBackend->getPrecision()); mGWSl2Norm = {128, (uint32_t)(mNumKHeads * UP_DIV(seqLen, 4)), (uint32_t)(batch * 2)}; mLWSl2Norm = {128, 1, 1}; uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernell2Norm->get().setArg(idx++, openCLBuffer(mConvOut.get())); // conv_out ret |= mKernell2Norm->get().setArg(idx++, openCLBuffer(mConvOut.get())); // conv_out ret |= mKernell2Norm->get().setArg(idx++, convDim); ret |= mKernell2Norm->get().setArg(idx++, dk); ret |= mKernell2Norm->get().setArg(idx++, 1); ret |= mKernell2Norm->get().setArg(idx++, key_dim); ret |= mKernell2Norm->get().setArg(idx++, seqLen); MNN_CHECK_CL_SUCCESS(ret, "setArg l2 norm"); } // Kernel 3: gated_delta_rule { mGWSGatedDeltaRule = {(uint32_t)local_size, (uint32_t)UP_DIV(dv, 4) * H * batch}; uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelGatedDeltaRule->get().setArg(idx++, openCLBuffer(mConvOut.get())); // conv_out ret |= mKernelGatedDeltaRule->get().setArg(idx++, openCLBuffer(inputs[1])); // gate ret |= mKernelGatedDeltaRule->get().setArg(idx++, openCLBuffer(inputs[2])); // beta ret |= mKernelGatedDeltaRule->get().setArg(idx++, openCLBuffer(mStateCache->mRecurrentState.get())); // recurrent_state id = 6 ret |= mKernelGatedDeltaRule->get().setArg(idx++, openCLBuffer(outputs[0])); // attn_out ret |= mKernelGatedDeltaRule->get().setArg(idx++, batch); ret |= mKernelGatedDeltaRule->get().setArg(idx++, convDim); ret |= mKernelGatedDeltaRule->get().setArg(idx++, seqLen); ret |= mKernelGatedDeltaRule->get().setArg(idx++, mNumKHeads); ret |= mKernelGatedDeltaRule->get().setArg(idx++, mNumVHeads); ret |= mKernelGatedDeltaRule->get().setArg(idx++, dk); ret |= mKernelGatedDeltaRule->get().setArg(idx++, dv); ret |= mKernelGatedDeltaRule->get().setArg(idx++, key_dim); ret |= mKernelGatedDeltaRule->get().setArg(idx++, val_dim); ret |= mKernelGatedDeltaRule->get().setArg(idx++, gqa_factor); ret |= mKernelGatedDeltaRule->get().setArg(idx++, qScale); MNN_CHECK_CL_SUCCESS(ret, "setArg linear_attn_gated_delta_rule"); maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelGatedDeltaRule)); mLWSGatedDeltaRule = {(uint32_t)local_size, 1}; } // Round up global work sizes to multiples of local work sizes mGWSConvSilu[0] = ROUND_UP(mGWSConvSilu[0], mLWSConvSilu[0]); if (convStateSize > 0) { mGWSConvStateUpdate[0] = ROUND_UP(mGWSConvStateUpdate[0], mLWSConvStateUpdate[0]); } mGWSGatedDeltaRule[0] = ROUND_UP(mGWSGatedDeltaRule[0], mLWSGatedDeltaRule[0]); // Record kernels for queue recording optimization mOpenCLBackend->startRecord(mRecording); mOpenCLBackend->recordKernel3d(mKernelConvSilu, mGWSConvSilu, mLWSConvSilu); if (convStateSize > 0) { mOpenCLBackend->recordKernel3d(mKernelConvStateUpdate, mGWSConvStateUpdate, mLWSConvStateUpdate); } if(mUseQKL2Norm){ mOpenCLBackend->recordKernel3d(mKernell2Norm, mGWSl2Norm, mLWSl2Norm); } mOpenCLBackend->recordKernel2d(mKernelGatedDeltaRule, mGWSGatedDeltaRule, mLWSGatedDeltaRule); mOpenCLBackend->endRecord(mRecording); return NO_ERROR; } ErrorCode LinearAttentionBufExecution::onResizeChunkedPrefill( const std::vector &inputs, const std::vector &outputs) { auto qkv = inputs[0]; int batch = qkv->length(0); int convDim = qkv->length(1); int seqLen = qkv->length(2); int H = mNumVHeads; int dk = mHeadKDim; int dv = mHeadVDim; int K_conv = inputs[3]->length(2); int convStateSize = K_conv - 1; int key_dim = mNumKHeads * dk; int gqa_factor = (mNumVHeads > mNumKHeads) ? (mNumVHeads / mNumKHeads) : 1; float qScale = 1.0f / sqrt((float)dk); auto runtime = mOpenCLBackend->getOpenCLRuntime(); int bytesPerElement = mOpenCLBackend->fpBytes(); // ─── Persistent state buffers (STATIC): allocate once, shared via onClone ─── if (mStateCache->mRecurrentState.get() == nullptr) { int rnnSize = batch * H * dk * dv; mStateCache->mRecurrentState.reset(Tensor::createDevice({rnnSize})); mStateCache->mRecurrentStateTune.reset(Tensor::createDevice({rnnSize})); bool success = backend()->onAcquireBuffer(mStateCache->mRecurrentState.get(), Backend::STATIC); if (!success) return OUT_OF_MEMORY; { cl_int res; int bufferBytes = rnnSize * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mRecurrentState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mRecurrentState.get()), mapPtr); } } if (convStateSize > 0) { int convStateTotal = batch * convDim * convStateSize; mStateCache->mConvState.reset(Tensor::createDevice({convStateTotal})); success &= backend()->onAcquireBuffer(mStateCache->mConvState.get(), Backend::STATIC); if (!success) return OUT_OF_MEMORY; cl_int res; int bufferBytes = convStateTotal * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mConvState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mConvState.get()), mapPtr); } } } else { // Prefill (seqLen > 1): reset state for new sequence { cl_int res; int bufferBytes = mStateCache->mRecurrentState->elementSize() * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mRecurrentState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mRecurrentState.get()), mapPtr); } } if (mStateCache->mConvState.get() != nullptr) { cl_int res; int bufferBytes = mStateCache->mConvState->elementSize() * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mConvState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mConvState.get()), mapPtr); } } } // ─── Allocate temporary buffers ─── // IMPORTANT: All DYNAMIC buffers that are used together during execution must have // overlapping lifetimes (acquire all before releasing any) to prevent the memory // planner from aliasing them. mConvOutPrefill is read by C2-C5 and C7 concurrently // with chunk buffers, so they must all be alive simultaneously. mConvOutPrefill.reset(Tensor::createDevice({batch * convDim * seqLen})); mOpenCLBackend->onAcquireBuffer(mStateCache->mRecurrentStateTune.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mStateCache->mRecurrentStateTune.get(), Backend::DYNAMIC); int chunkSize = mChunkSize; int numChunks = UP_DIV(seqLen, chunkSize); mNumChunks = numChunks; // Allocate intermediate buffers // Critical buffers use float32 for precision (allocate 2x elements in half mode to get 4N bytes = N floats) // Non-critical buffers use FLOAT (matches onAcquireBuffer precision) int fpBytes = mOpenCLBackend->fpBytes(); auto f32Elems = [fpBytes](int n) { return (n * 4 + fpBytes - 1) / fpBytes; }; mGCumsumBuf.reset(Tensor::createDevice({f32Elems(batch * H * numChunks * chunkSize)})); mAttnMatrixBuf.reset(Tensor::createDevice({f32Elems(batch * H * numChunks * chunkSize * chunkSize)})); mVCorrectedBuf.reset(Tensor::createDevice({f32Elems(batch * H * numChunks * chunkSize * dv)})); mKCumdecayBuf.reset(Tensor::createDevice({f32Elems(batch * H * numChunks * chunkSize * dk)})); mVNewBuf.reset(Tensor::createDevice({f32Elems(batch * H * chunkSize * dv)})); // Acquire all buffers used concurrently during execution BEFORE releasing any mOpenCLBackend->onAcquireBuffer(mConvOutPrefill.get(), Backend::DYNAMIC); mOpenCLBackend->onAcquireBuffer(mGCumsumBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onAcquireBuffer(mAttnMatrixBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onAcquireBuffer(mVCorrectedBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onAcquireBuffer(mKCumdecayBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onAcquireBuffer(mVNewBuf.get(), Backend::DYNAMIC); // Release all together — planner now sees overlapping lifetimes, no aliasing mOpenCLBackend->onReleaseBuffer(mConvOutPrefill.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mGCumsumBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mAttnMatrixBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mVCorrectedBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mKCumdecayBuf.get(), Backend::DYNAMIC); mOpenCLBackend->onReleaseBuffer(mVNewBuf.get(), Backend::DYNAMIC); // ─── Build common kernels for prefill ─── std::set buildOptions; int local_size = 16; buildOptions.emplace("-DLOCAL_SIZE=" + std::to_string(local_size)); buildOptions.emplace("-DK_SIZE=" + std::to_string(dv)); // Conv1D + SiLU mKernelConvSiluPrefill = runtime->buildKernel("linear_attention_buf", "linear_attn_conv_silu", buildOptions, mOpenCLBackend->getPrecision()); int totalConvSilu = batch * convDim * seqLen; mGWSConvSiluPrefill = {(uint32_t)totalConvSilu, 1, 1}; { auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelConvSiluPrefill)); uint32_t lwsConv = std::min(maxWorkGroupSize, (uint32_t)256); lwsConv = std::min(lwsConv, (uint32_t)totalConvSilu); mLWSConvSiluPrefill = {lwsConv, 1, 1}; } { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelConvSiluPrefill->get().setArg(idx++, totalConvSilu); ret |= mKernelConvSiluPrefill->get().setArg(idx++, openCLBuffer(inputs[0])); ret |= mKernelConvSiluPrefill->get().setArg(idx++, openCLBuffer(mStateCache->mConvState.get())); ret |= mKernelConvSiluPrefill->get().setArg(idx++, openCLBuffer(inputs[3])); ret |= mKernelConvSiluPrefill->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernelConvSiluPrefill->get().setArg(idx++, batch); ret |= mKernelConvSiluPrefill->get().setArg(idx++, convDim); ret |= mKernelConvSiluPrefill->get().setArg(idx++, seqLen); ret |= mKernelConvSiluPrefill->get().setArg(idx++, K_conv); ret |= mKernelConvSiluPrefill->get().setArg(idx++, convStateSize); MNN_CHECK_CL_SUCCESS(ret, "setArg linear_attn_conv_silu (prefill)"); } mGWSConvSiluPrefill[0] = ROUND_UP(mGWSConvSiluPrefill[0], mLWSConvSiluPrefill[0]); // Conv state update if (convStateSize > 0) { mKernelConvStateUpdatePrefill = runtime->buildKernel("linear_attention_buf", "linear_attn_conv_state_update", buildOptions, mOpenCLBackend->getPrecision()); int totalConvUpdate = batch * convDim * convStateSize; mGWSConvStateUpdatePrefill = {(uint32_t)totalConvUpdate, 1, 1}; { auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelConvStateUpdatePrefill)); uint32_t lwsUpdate = std::min(maxWorkGroupSize, (uint32_t)256); lwsUpdate = std::min(lwsUpdate, (uint32_t)totalConvUpdate); mLWSConvStateUpdatePrefill = {lwsUpdate, 1, 1}; } { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, totalConvUpdate); ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, openCLBuffer(inputs[0])); ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, openCLBuffer(mStateCache->mConvState.get())); ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, batch); ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, convDim); ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, seqLen); ret |= mKernelConvStateUpdatePrefill->get().setArg(idx++, convStateSize); MNN_CHECK_CL_SUCCESS(ret, "setArg linear_attn_conv_state_update (prefill)"); } mGWSConvStateUpdatePrefill[0] = ROUND_UP(mGWSConvStateUpdatePrefill[0], mLWSConvStateUpdatePrefill[0]); } // L2 norm if (mUseQKL2Norm) { auto l2BuildOptions = buildOptions; l2BuildOptions.emplace("-DUSE_VEC"); mKernell2NormPrefill = runtime->buildKernel("linear_attention_buf", "l2_norm", l2BuildOptions, mOpenCLBackend->getPrecision()); mGWSl2NormPrefill = {128, (uint32_t)(mNumKHeads * UP_DIV(seqLen, 4)), (uint32_t)(batch * 2)}; mLWSl2NormPrefill = {128, 1, 1}; uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernell2NormPrefill->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernell2NormPrefill->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernell2NormPrefill->get().setArg(idx++, convDim); ret |= mKernell2NormPrefill->get().setArg(idx++, dk); ret |= mKernell2NormPrefill->get().setArg(idx++, 1); ret |= mKernell2NormPrefill->get().setArg(idx++, key_dim); ret |= mKernell2NormPrefill->get().setArg(idx++, seqLen); MNN_CHECK_CL_SUCCESS(ret, "setArg l2 norm (prefill)"); } // ─── Build chunked prefill kernels ─── std::set chunkOpts = buildOptions; chunkOpts.emplace("-DCHUNK_PREFILL"); chunkOpts.emplace("-DCHUNK_SIZE=" + std::to_string(chunkSize)); // C1: chunk_g_cumsum mKernelChunkGCumsum = runtime->buildKernel("linear_attention_buf", "chunk_g_cumsum", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkGCumsum = {(uint32_t)H, (uint32_t)numChunks, (uint32_t)batch}; mLWSChunkGCumsum = {1, 1, 1}; { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkGCumsum->get().setArg(idx++, openCLBuffer(inputs[1])); // gate ret |= mKernelChunkGCumsum->get().setArg(idx++, openCLBuffer(mGCumsumBuf.get())); // g_cumsum ret |= mKernelChunkGCumsum->get().setArg(idx++, H); ret |= mKernelChunkGCumsum->get().setArg(idx++, seqLen); ret |= mKernelChunkGCumsum->get().setArg(idx++, numChunks); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_g_cumsum"); } { { // C2: chunk_build_neumann_attn mKernelChunkNeumannAttn0 = runtime->buildKernel("linear_attention_buf", "chunk_build_neumann_attn_step0", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkNeumannAttn0 = {(uint32_t)chunkSize * chunkSize, (uint32_t)(H * numChunks), (uint32_t)batch}; uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, openCLBuffer(inputs[2])); // beta ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, openCLBuffer(mGCumsumBuf.get())); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, openCLBuffer(mAttnMatrixBuf.get())); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, batch); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, convDim); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, seqLen); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, H); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, dk); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, key_dim); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, gqa_factor); ret |= mKernelChunkNeumannAttn0->get().setArg(idx++, numChunks); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_build_neumann_attn_step0"); auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelChunkNeumannAttn0)); mLWSChunkNeumannAttn0 = localWS3DDefault(mGWSChunkNeumannAttn0, maxWorkGroupSize, mOpenCLBackend->getOpenCLRuntime(), "chunk_build_neumann_attn_step0", mKernelChunkNeumannAttn0, mOpenCLBackend->getCLTuneLevel(), "linear_attention_buf").first; } { // C2: chunk_build_neumann_attn mKernelChunkNeumannAttn1 = runtime->buildKernel("linear_attention_buf", "chunk_build_neumann_attn_step1", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkNeumannAttn1 = {(uint32_t)chunkSize, (uint32_t)(H * numChunks), (uint32_t)batch}; mLWSChunkNeumannAttn1 = {(uint32_t)chunkSize, 1, 1}; uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, openCLBuffer(mAttnMatrixBuf.get())); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, batch); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, convDim); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, seqLen); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, H); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, dk); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, key_dim); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, gqa_factor); ret |= mKernelChunkNeumannAttn1->get().setArg(idx++, numChunks); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_build_neumann_attn_step1"); } } // C3: chunk_correct_v mKernelChunkCorrectV = runtime->buildKernel("linear_attention_buf", "chunk_correct_v", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkCorrectV = {(uint32_t)UP_DIV(dv, 4), (uint32_t)(chunkSize * numChunks), (uint32_t)(batch * H)}; { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkCorrectV->get().setArg(idx++, openCLBuffer(mAttnMatrixBuf.get())); ret |= mKernelChunkCorrectV->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernelChunkCorrectV->get().setArg(idx++, openCLBuffer(inputs[2])); // beta ret |= mKernelChunkCorrectV->get().setArg(idx++, openCLBuffer(mGCumsumBuf.get())); ret |= mKernelChunkCorrectV->get().setArg(idx++, openCLBuffer(mVCorrectedBuf.get())); ret |= mKernelChunkCorrectV->get().setArg(idx++, openCLBuffer(mKCumdecayBuf.get())); ret |= mKernelChunkCorrectV->get().setArg(idx++, mGWSChunkCorrectV[0]); ret |= mKernelChunkCorrectV->get().setArg(idx++, mGWSChunkCorrectV[1]); ret |= mKernelChunkCorrectV->get().setArg(idx++, mGWSChunkCorrectV[2]); ret |= mKernelChunkCorrectV->get().setArg(idx++, convDim); ret |= mKernelChunkCorrectV->get().setArg(idx++, seqLen); ret |= mKernelChunkCorrectV->get().setArg(idx++, H); ret |= mKernelChunkCorrectV->get().setArg(idx++, dk); ret |= mKernelChunkCorrectV->get().setArg(idx++, dv); ret |= mKernelChunkCorrectV->get().setArg(idx++, key_dim); ret |= mKernelChunkCorrectV->get().setArg(idx++, gqa_factor); ret |= mKernelChunkCorrectV->get().setArg(idx++, numChunks); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_correct_v"); auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelChunkCorrectV)); mLWSChunkCorrectV = localWS3DDefault(mGWSChunkCorrectV, maxWorkGroupSize, mOpenCLBackend->getOpenCLRuntime(), "chunk_correct_v", mKernelChunkCorrectV, mOpenCLBackend->getCLTuneLevel(), "linear_attention_buf").first; } // C5: chunk_qk_attn (reuses attn_matrix buffer) mKernelChunkQKAttn = runtime->buildKernel("linear_attention_buf", "chunk_qk_attn", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkQKAttn = {(uint32_t)chunkSize, (uint32_t)(chunkSize * numChunks), (uint32_t)(batch * H)}; { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkQKAttn->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernelChunkQKAttn->get().setArg(idx++, openCLBuffer(mGCumsumBuf.get())); ret |= mKernelChunkQKAttn->get().setArg(idx++, openCLBuffer(mAttnMatrixBuf.get())); // overwrite ret |= mKernelChunkQKAttn->get().setArg(idx++, mGWSChunkQKAttn[0]); ret |= mKernelChunkQKAttn->get().setArg(idx++, mGWSChunkQKAttn[1]); ret |= mKernelChunkQKAttn->get().setArg(idx++, mGWSChunkQKAttn[2]); ret |= mKernelChunkQKAttn->get().setArg(idx++, convDim); ret |= mKernelChunkQKAttn->get().setArg(idx++, seqLen); ret |= mKernelChunkQKAttn->get().setArg(idx++, H); ret |= mKernelChunkQKAttn->get().setArg(idx++, dk); ret |= mKernelChunkQKAttn->get().setArg(idx++, key_dim); ret |= mKernelChunkQKAttn->get().setArg(idx++, gqa_factor); ret |= mKernelChunkQKAttn->get().setArg(idx++, numChunks); ret |= mKernelChunkQKAttn->get().setArg(idx++, qScale); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_qk_attn"); auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelChunkQKAttn)); mLWSChunkQKAttn = localWS3DDefault(mGWSChunkQKAttn, maxWorkGroupSize, mOpenCLBackend->getOpenCLRuntime(), "chunk_qk_attn", mKernelChunkQKAttn, mOpenCLBackend->getCLTuneLevel(), "linear_attention_buf").first; } // C6: chunk_compute_vnew (per-chunk, chunk_idx=11 set dynamically) mKernelChunkVnew = runtime->buildKernel("linear_attention_buf", "chunk_compute_vnew", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkVnew = {(uint32_t)UP_DIV(dv, 4), (uint32_t)chunkSize, (uint32_t)(batch * H)}; { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkVnew->get().setArg(idx++, openCLBuffer(mVCorrectedBuf.get())); ret |= mKernelChunkVnew->get().setArg(idx++, openCLBuffer(mKCumdecayBuf.get())); ret |= mKernelChunkVnew->get().setArg(idx++, openCLBuffer(mStateCache->mRecurrentStateTune.get())); // arg 2: state (tune first) ret |= mKernelChunkVnew->get().setArg(idx++, openCLBuffer(mVNewBuf.get())); ret |= mKernelChunkVnew->get().setArg(idx++, mGWSChunkVnew[0]); ret |= mKernelChunkVnew->get().setArg(idx++, mGWSChunkVnew[1]); ret |= mKernelChunkVnew->get().setArg(idx++, mGWSChunkVnew[2]); ret |= mKernelChunkVnew->get().setArg(idx++, dk); ret |= mKernelChunkVnew->get().setArg(idx++, dv); ret |= mKernelChunkVnew->get().setArg(idx++, H); ret |= mKernelChunkVnew->get().setArg(idx++, numChunks); ret |= mKernelChunkVnew->get().setArg(idx++, 0); // chunk_idx placeholder MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_compute_vnew"); auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelChunkVnew)); mLWSChunkVnew = localWS3DDefault(mGWSChunkVnew, maxWorkGroupSize, mOpenCLBackend->getOpenCLRuntime(), "chunk_compute_vnew", mKernelChunkVnew, mOpenCLBackend->getCLTuneLevel(), "linear_attention_buf").first; // Swap to real state buffer after tuning ret |= mKernelChunkVnew->get().setArg(2, openCLBuffer(mStateCache->mRecurrentState.get())); } // C6.5: chunk_output (per-chunk, chunk_idx=17 set dynamically) mKernelChunkOutput = runtime->buildKernel("linear_attention_buf", "chunk_output", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkOutput = {(uint32_t)UP_DIV(dv, 4) * chunkSize, (uint32_t)H, (uint32_t)batch}; { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkOutput->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernelChunkOutput->get().setArg(idx++, openCLBuffer(mAttnMatrixBuf.get())); // qk_attn after C5 ret |= mKernelChunkOutput->get().setArg(idx++, openCLBuffer(mVNewBuf.get())); ret |= mKernelChunkOutput->get().setArg(idx++, openCLBuffer(mGCumsumBuf.get())); ret |= mKernelChunkOutput->get().setArg(idx++, openCLBuffer(mStateCache->mRecurrentState.get())); // arg 4: state (tune first) ret |= mKernelChunkOutput->get().setArg(idx++, openCLBuffer(outputs[0])); ret |= mKernelChunkOutput->get().setArg(idx++, mGWSChunkOutput[0]); ret |= mKernelChunkOutput->get().setArg(idx++, mGWSChunkOutput[1]); ret |= mKernelChunkOutput->get().setArg(idx++, mGWSChunkOutput[2]); ret |= mKernelChunkOutput->get().setArg(idx++, convDim); ret |= mKernelChunkOutput->get().setArg(idx++, seqLen); ret |= mKernelChunkOutput->get().setArg(idx++, H); ret |= mKernelChunkOutput->get().setArg(idx++, dk); ret |= mKernelChunkOutput->get().setArg(idx++, dv); ret |= mKernelChunkOutput->get().setArg(idx++, key_dim); ret |= mKernelChunkOutput->get().setArg(idx++, gqa_factor); ret |= mKernelChunkOutput->get().setArg(idx++, numChunks); ret |= mKernelChunkOutput->get().setArg(idx++, 0); // chunk_idx placeholder ret |= mKernelChunkOutput->get().setArg(idx++, qScale); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_output"); auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelChunkOutput)); mLWSChunkOutput = localWS3DDefault(mGWSChunkOutput, maxWorkGroupSize, mOpenCLBackend->getOpenCLRuntime(), "chunk_output", mKernelChunkOutput, mOpenCLBackend->getCLTuneLevel(), "linear_attention_buf").first; } // C7: chunk_output_state_update (per-chunk, chunk_idx=17 set dynamically) mKernelChunkOutputUpdate = runtime->buildKernel("linear_attention_buf", "chunk_output_state_update", chunkOpts, mOpenCLBackend->getPrecision()); mGWSChunkOutputUpdate = {(uint32_t)UP_DIV(dv, 4) * dk, (uint32_t)H, (uint32_t)batch}; { uint32_t idx = 0; cl_int ret = CL_SUCCESS; ret |= mKernelChunkOutputUpdate->get().setArg(idx++, openCLBuffer(mConvOutPrefill.get())); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, openCLBuffer(mAttnMatrixBuf.get())); // qk_attn after C5 ret |= mKernelChunkOutputUpdate->get().setArg(idx++, openCLBuffer(mVNewBuf.get())); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, openCLBuffer(mGCumsumBuf.get())); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, openCLBuffer(mStateCache->mRecurrentStateTune.get())); // arg 4: state (tune first) ret |= mKernelChunkOutputUpdate->get().setArg(idx++, openCLBuffer(outputs[0])); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, mGWSChunkOutputUpdate[0]); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, mGWSChunkOutputUpdate[1]); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, mGWSChunkOutputUpdate[2]); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, convDim); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, seqLen); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, H); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, dk); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, dv); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, key_dim); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, gqa_factor); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, numChunks); ret |= mKernelChunkOutputUpdate->get().setArg(idx++, 0); // chunk_idx placeholder ret |= mKernelChunkOutputUpdate->get().setArg(idx++, qScale); MNN_CHECK_CL_SUCCESS(ret, "setArg chunk_output_state_update"); auto maxWorkGroupSize = static_cast(runtime->getMaxWorkGroupSize(mKernelChunkOutputUpdate)); mLWSChunkOutputUpdate = localWS3DDefault(mGWSChunkOutputUpdate, maxWorkGroupSize, mOpenCLBackend->getOpenCLRuntime(), "chunk_output_state_update", mKernelChunkOutputUpdate, mOpenCLBackend->getCLTuneLevel(), "linear_attention_buf").first; // Swap to real state buffer after tuning ret |= mKernelChunkOutputUpdate->get().setArg(4, openCLBuffer(mStateCache->mRecurrentState.get())); } // Round up chunked GWS for (auto& gws_lws : std::vector*, std::vector*>>{ {&mGWSChunkCorrectV, &mLWSChunkCorrectV}, {&mGWSChunkNeumannAttn0, &mLWSChunkNeumannAttn0}, {&mGWSChunkNeumannAttn1, &mLWSChunkNeumannAttn1}, {&mGWSChunkQKAttn, &mLWSChunkQKAttn}, {&mGWSChunkVnew, &mLWSChunkVnew},{&mGWSChunkOutput, &mLWSChunkOutput}, {&mGWSChunkOutputUpdate, &mLWSChunkOutputUpdate}}) { for (int d = 0; d < 3; ++d) { (*gws_lws.first)[d] = ROUND_UP((*gws_lws.first)[d], std::max((uint32_t)1, (*gws_lws.second)[d])); } } return NO_ERROR; } ErrorCode LinearAttentionBufExecution::onExecuteChunkedPrefill(const std::vector &inputs, const std::vector &outputs) { auto runtime = mOpenCLBackend->getOpenCLRuntime(); int convStateSize = inputs[3]->length(2) - 1; #ifdef ENABLE_OPENCL_TIME_PROFILER { cl::Event event; run3DKernelDefault(mKernelConvSiluPrefill, mGWSConvSiluPrefill, mLWSConvSiluPrefill, runtime, &event); runtime->pushEvent({"linear_attn_conv_silu", event}); } if (convStateSize > 0) { cl::Event event; run3DKernelDefault(mKernelConvStateUpdatePrefill, mGWSConvStateUpdatePrefill, mLWSConvStateUpdatePrefill, runtime, &event); runtime->pushEvent({"linear_attn_conv_state_update", event}); } if (mUseQKL2Norm) { cl::Event event; run3DKernelDefault(mKernell2NormPrefill, mGWSl2NormPrefill, mLWSl2NormPrefill, runtime, &event); runtime->pushEvent({"l2_norm", event}); } { cl::Event e; run3DKernelDefault(mKernelChunkGCumsum, mGWSChunkGCumsum, mLWSChunkGCumsum, runtime, &e); runtime->pushEvent({"chunk_g_cumsum", e}); } { cl::Event e; run3DKernelDefault(mKernelChunkNeumannAttn0, mGWSChunkNeumannAttn0, mLWSChunkNeumannAttn0, runtime, &e); runtime->pushEvent({"chunk_build_neumann_attn0", e}); } { cl::Event e; run3DKernelDefault(mKernelChunkNeumannAttn1, mGWSChunkNeumannAttn1, mLWSChunkNeumannAttn1, runtime, &e); runtime->pushEvent({"chunk_build_neumann_attn1", e}); } { cl::Event e; run3DKernelDefault(mKernelChunkCorrectV, mGWSChunkCorrectV, mLWSChunkCorrectV, runtime, &e); runtime->pushEvent({"chunk_correct_v", e}); } { cl::Event e; run3DKernelDefault(mKernelChunkQKAttn, mGWSChunkQKAttn, mLWSChunkQKAttn, runtime, &e); runtime->pushEvent({"chunk_qk_attn", e}); } for (int c = 0; c < mNumChunks; ++c) { mKernelChunkVnew->get().setArg(11, c); mKernelChunkOutput->get().setArg(17, c); mKernelChunkOutputUpdate->get().setArg(17, c); { cl::Event e; run3DKernelDefault(mKernelChunkVnew, mGWSChunkVnew, mLWSChunkVnew, runtime, &e); runtime->pushEvent({"chunk_vnew_" + std::to_string(c), e}); } { cl::Event e; runKernel2D(mKernelChunkOutput, mGWSChunkOutput, mLWSChunkOutput, runtime, &e); runtime->pushEvent({"chunk_output_" + std::to_string(c), e}); } { cl::Event e; run3DKernelDefault(mKernelChunkOutputUpdate, mGWSChunkOutputUpdate, mLWSChunkOutputUpdate, runtime, &e); runtime->pushEvent({"chunk_update" + std::to_string(c), e}); } } #else // Common kernels run3DKernelDefault(mKernelConvSiluPrefill, mGWSConvSiluPrefill, mLWSConvSiluPrefill, runtime); if (convStateSize > 0) { run3DKernelDefault(mKernelConvStateUpdatePrefill, mGWSConvStateUpdatePrefill, mLWSConvStateUpdatePrefill, runtime); } if (mUseQKL2Norm) { run3DKernelDefault(mKernell2NormPrefill, mGWSl2NormPrefill, mLWSl2NormPrefill, runtime); } // Chunked prefill: C1 → C2 → C3, C4 → C5 → loop(C6, C7) run3DKernelDefault(mKernelChunkGCumsum, mGWSChunkGCumsum, mLWSChunkGCumsum, runtime); run3DKernelDefault(mKernelChunkNeumannAttn0, mGWSChunkNeumannAttn0, mLWSChunkNeumannAttn0, runtime); run3DKernelDefault(mKernelChunkNeumannAttn1, mGWSChunkNeumannAttn1, mLWSChunkNeumannAttn1, runtime); run3DKernelDefault(mKernelChunkCorrectV, mGWSChunkCorrectV, mLWSChunkCorrectV, runtime); run3DKernelDefault(mKernelChunkQKAttn, mGWSChunkQKAttn, mLWSChunkQKAttn, runtime); for (int c = 0; c < mNumChunks; ++c) { mKernelChunkVnew->get().setArg(11, c); // chunk_idx at position 11 mKernelChunkOutput->get().setArg(17, c); // chunk_idx at position 17 mKernelChunkOutputUpdate->get().setArg(17, c); // chunk_idx at position 17 run3DKernelDefault(mKernelChunkVnew, mGWSChunkVnew, mLWSChunkVnew, runtime); runKernel2D(mKernelChunkOutput, mGWSChunkOutput, mLWSChunkOutput, runtime); run3DKernelDefault(mKernelChunkOutputUpdate, mGWSChunkOutputUpdate, mLWSChunkOutputUpdate, runtime); } #endif return NO_ERROR; } ErrorCode LinearAttentionBufExecution::onExecute(const std::vector &inputs, const std::vector &outputs) { // onResize() may be skipped when shapes are unchanged. Ensure state is reset here too. int seqLen = inputs[0]->length(2); if (seqLen > 1 && mMeta != nullptr && mMeta->previous == mMeta->remove) { bool loadingFromDisk = (mMeta->file_flag == KVMeta::PendingRead && mMeta->file_name.size() > 0); if (!loadingFromDisk) { auto runtime = mOpenCLBackend->getOpenCLRuntime(); int bytesPerElement = mOpenCLBackend->fpBytes(); if (mStateCache->mConvState.get() != nullptr) { cl_int res; int bufferBytes = mStateCache->mConvState->elementSize() * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mConvState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mConvState.get()), mapPtr); } } { cl_int res; int bufferBytes = mStateCache->mRecurrentState->elementSize() * bytesPerElement; void* mapPtr = runtime->commandQueue().enqueueMapBuffer( openCLBuffer(mStateCache->mRecurrentState.get()), true, CL_MAP_WRITE, 0, bufferBytes, nullptr, nullptr, &res); if (mapPtr != nullptr && res == CL_SUCCESS) { ::memset(mapPtr, 0, bufferBytes); runtime->commandQueue().enqueueUnmapMemObject(openCLBuffer(mStateCache->mRecurrentState.get()), mapPtr); } } } } if (mUseChunkedPrefill) { return onExecuteChunkedPrefill(inputs, outputs); } auto runtime = mOpenCLBackend->getOpenCLRuntime(); int convStateSize = inputs[3]->length(2) - 1; #ifdef ENABLE_OPENCL_TIME_PROFILER { cl::Event event; run3DKernelDefault(mKernelConvSilu, mGWSConvSilu, mLWSConvSilu, runtime, &event); runtime->pushEvent({"linear_attn_conv_silu", event}); } if (convStateSize > 0) { cl::Event event; run3DKernelDefault(mKernelConvStateUpdate, mGWSConvStateUpdate, mLWSConvStateUpdate, runtime, &event); runtime->pushEvent({"linear_attn_conv_state_update", event}); } if(mUseQKL2Norm){ cl::Event event; run3DKernelDefault(mKernell2Norm, mGWSl2Norm, mLWSl2Norm, runtime, &event); runtime->pushEvent({"l2_norm", event}); } { cl::Event event; runKernel2D(mKernelGatedDeltaRule, mGWSGatedDeltaRule, mLWSGatedDeltaRule, runtime, &event); runtime->pushEvent({"linear_attn_gated_delta_rule", event}); } #else if(mOpenCLBackend->isUseRecordQueue()){ mOpenCLBackend->addRecord(mRecording, mOpRecordUpdateInfo); return NO_ERROR; } run3DKernelDefault(mKernelConvSilu, mGWSConvSilu, mLWSConvSilu, runtime); if (convStateSize > 0) { run3DKernelDefault(mKernelConvStateUpdate, mGWSConvStateUpdate, mLWSConvStateUpdate, runtime); } if(mUseQKL2Norm){ run3DKernelDefault(mKernell2Norm, mGWSl2Norm, mLWSl2Norm, runtime); } runKernel2D(mKernelGatedDeltaRule, mGWSGatedDeltaRule, mLWSGatedDeltaRule, runtime); #endif return NO_ERROR; } bool LinearAttentionBufExecution::onClone(Backend* bn, const Op* op, Execution** dst) { if (nullptr == dst) { return true; } auto exe = new LinearAttentionBufExecution(op, bn); // Share persistent state buffers between prefill and decode Executions exe->mStateCache = mStateCache; *dst = exe; return true; } class LinearAttentionBufCreator : public OpenCLBackend::Creator { public: virtual Execution *onCreate(const std::vector &inputs, const std::vector &outputs, const MNN::Op *op, Backend *backend) const override { for (int i = 0; i < inputs.size(); ++i) { TensorUtils::setTensorSupportPack(inputs[i], false); } for (int i = 0; i < outputs.size(); ++i) { TensorUtils::setTensorSupportPack(outputs[i], false); } OPENCL_CREATOR_CHECK(new LinearAttentionBufExecution(op, backend)); } }; REGISTER_OPENCL_OP_CREATOR_TRANSFORMER(LinearAttentionBufCreator, OpType_LinearAttention, BUFFER); } // namespace OpenCL } // namespace MNN #endif /* MNN_SUPPORT_TRANSFORMER_FUSE */