#include "VulkanConv1x1CoopA8.hpp" #include "core/TensorUtils.hpp" #include "core/Macro.h" #include "VulkanBackend.hpp" #include "backend/vulkan/vulkan/vulkan_wrapper.h" #include #include #include #ifdef ENABLE_VULKAN_TIME_PROFILE #include "backend/vulkan/component/VulkanTimeProfiler.hpp" #endif namespace MNN { namespace { struct QuantWeightPrepareParams { uint32_t ci; uint32_t co; uint32_t padN; uint32_t weightStride; uint32_t srcBytes; }; struct QuantMetaPrepareParams { uint32_t co; uint32_t padN; uint32_t blockCount; uint32_t blockStride; uint32_t soSize; uint32_t alphaSize; }; struct SumKPrepareParams { uint32_t padN; uint32_t weightStride; }; // Builds the static GPU buffers shared by decode + prefill paths: // - quantWeightBuffer: INT8: [padN, padK/4] uint32 (4x int8 packed). // INT4: [padN, padK/8] uint32 (8x int4 packed, // unsigned 0..15 with host +8 offset; padding // nibbles are 0x8 -> decoded 0). // - quantMetaBuffer: [padN, 2] FP interleaved (scale, offset). // - sumWqBuffer: [padN] int32 = sum_k Wq[n, k], padding rows zero. // Per-channel asym only — caller must guard. static bool _prepareStaticBuffersGPU(VulkanBackend* vkBn, const ConvolutionCommon::Int8Common* quantCommon, bool useFP16, int ci, int co, uint32_t padN, uint32_t padK, std::shared_ptr& quantWeightBuffer, std::shared_ptr& quantMetaBuffer, std::shared_ptr& sumWqBuffer) { if (nullptr == vkBn || nullptr == quantCommon || nullptr == quantCommon->weight.get()) { return false; } MNN_ASSERT(quantCommon->asymmetric); const bool isInt4 = quantCommon->canUseInt4; if (isInt4) { MNN_ASSERT(padK % 8u == 0u); } const int soSize = 2; const int alphaSize = quantCommon->alpha.size(); const int blockCount = std::max(1, alphaSize / std::max(1, co * soSize)); if (blockCount != 1) { return false; } const uint32_t blockStride = 1u; const uint32_t decodeWeightStrideWords = isInt4 ? (padK / 8u) : (padK / 4u); const int8_t* qWeight = quantCommon->weight.get(); const size_t rawWeightBytes = static_cast(quantCommon->weight.size()); const size_t alignedWeightBytes = std::max(4u, ALIGN_UP4(rawWeightBytes)); const size_t decodeWeightBytes = static_cast(padN) * static_cast(decodeWeightStrideWords) * sizeof(uint32_t); const size_t metaBytes = static_cast(padN) * static_cast(blockStride) * 2u * (useFP16 ? sizeof(int16_t) : sizeof(float)); const size_t sumWqBytes = static_cast(padN) * sizeof(int32_t); const void* rawWeightSrc = qWeight; std::vector weightAlignedHost; if (alignedWeightBytes != rawWeightBytes) { weightAlignedHost.resize(alignedWeightBytes, 0); if (rawWeightBytes > 0u) { ::memcpy(weightAlignedHost.data(), qWeight, rawWeightBytes); } rawWeightSrc = weightAlignedHost.data(); } std::shared_ptr rawWeightBuffer(new VulkanBuffer( vkBn->getMemoryPool(), false, alignedWeightBytes, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); vkBn->copyToGPUBuffer(rawWeightSrc, rawWeightBuffer->buffer(), alignedWeightBytes, 0); quantWeightBuffer.reset(new VulkanBuffer(vkBn->getMemoryPool(), false, decodeWeightBytes, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); const float* alphaPtr = quantCommon->alpha.get(); const size_t rawAlphaBytes = static_cast(std::max(alphaSize, 1)) * sizeof(float); std::shared_ptr rawAlphaBuffer(new VulkanBuffer( vkBn->getMemoryPool(), false, rawAlphaBytes, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); if (alphaSize > 0 && nullptr != alphaPtr) { vkBn->copyToGPUBuffer(alphaPtr, rawAlphaBuffer->buffer(), static_cast(alphaSize) * sizeof(float), 0); } else { const float zero = 0.0f; vkBn->copyToGPUBuffer(&zero, rawAlphaBuffer->buffer(), sizeof(float), 0); } quantMetaBuffer.reset(new VulkanBuffer(vkBn->getMemoryPool(), false, metaBytes, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); sumWqBuffer.reset(new VulkanBuffer(vkBn->getMemoryPool(), false, sumWqBytes, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); const char* weightShader = isInt4 ? "glsl_conv1x1_int4_weight_prepare_comp" : "glsl_conv1x1_int8_weight_prepare_comp"; const char* metaShader = useFP16 ? "glsl_conv1x1_quant_meta_prepare_FP16_comp" : "glsl_conv1x1_quant_meta_prepare_comp"; const char* sumKShader = isInt4 ? "glsl_conv1x1_int4_weight_sumK_comp" : "glsl_conv1x1_int8_weight_sumK_comp"; std::vector twoBufTypes = { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, }; auto weightPipeline = vkBn->getPipeline(weightShader, twoBufTypes); auto metaPipeline = vkBn->getPipeline(metaShader, twoBufTypes); auto sumKPipeline = vkBn->getPipeline(sumKShader, twoBufTypes); if (nullptr == weightPipeline || nullptr == metaPipeline || nullptr == sumKPipeline) { return false; } std::shared_ptr weightSet(weightPipeline->createSet()); std::shared_ptr metaSet(metaPipeline->createSet()); std::shared_ptr sumKSet(sumKPipeline->createSet()); if (nullptr == weightSet.get() || nullptr == metaSet.get() || nullptr == sumKSet.get()) { return false; } std::shared_ptr prepareCmd(vkBn->getPool().allocBuffer()); prepareCmd->begin(0); { QuantWeightPrepareParams pc; pc.ci = static_cast(ci); pc.co = static_cast(co); pc.padN = padN; pc.weightStride = decodeWeightStrideWords; pc.srcBytes = static_cast(rawWeightBytes); weightSet->writeBuffer(rawWeightBuffer->buffer(), 0, rawWeightBuffer->size()); weightSet->writeBuffer(quantWeightBuffer->buffer(), 1, quantWeightBuffer->size()); weightPipeline->bind(prepareCmd->get(), weightSet->get()); vkCmdPushConstants(prepareCmd->get(), weightPipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc); vkCmdDispatch(prepareCmd->get(), UP_DIV(decodeWeightStrideWords, 16u), UP_DIV(padN, 16u), 1); prepareCmd->barrierSource(quantWeightBuffer->buffer(), 0, quantWeightBuffer->size()); } { QuantMetaPrepareParams pc; pc.co = static_cast(co); pc.padN = padN; pc.blockCount = static_cast(blockCount); pc.blockStride = blockStride; pc.soSize = static_cast(soSize); pc.alphaSize = static_cast(alphaSize); metaSet->writeBuffer(rawAlphaBuffer->buffer(), 0, rawAlphaBuffer->size()); metaSet->writeBuffer(quantMetaBuffer->buffer(), 1, quantMetaBuffer->size()); metaPipeline->bind(prepareCmd->get(), metaSet->get()); vkCmdPushConstants(prepareCmd->get(), metaPipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc); vkCmdDispatch(prepareCmd->get(), UP_DIV(blockStride, 16u), UP_DIV(padN, 16u), 1); prepareCmd->barrierSource(quantMetaBuffer->buffer(), 0, quantMetaBuffer->size()); } { SumKPrepareParams pc; pc.padN = padN; pc.weightStride = decodeWeightStrideWords; sumKSet->writeBuffer(quantWeightBuffer->buffer(), 0, quantWeightBuffer->size()); sumKSet->writeBuffer(sumWqBuffer->buffer(), 1, sumWqBuffer->size()); sumKPipeline->bind(prepareCmd->get(), sumKSet->get()); vkCmdPushConstants(prepareCmd->get(), sumKPipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc); vkCmdDispatch(prepareCmd->get(), UP_DIV(padN, 64u), 1, 1); } prepareCmd->end(); vkBn->getPool().submitAndWait(prepareCmd->get()); return true; } } // namespace VulkanConv1x1CoopA8::VulkanConv1x1CoopA8(VulkanBackend* backend, const Convolution2DCommon* convOption, const float* biasPtr, int ci, int co, VulkanDevice::CoopMatInfo coopMatInfo, std::shared_ptr quantInfo) : VulkanBasicExecution(backend), mCommon(convOption), mCi(ci), mCo(co), mQuantCommon(std::move(quantInfo)) { MNN_ASSERT(coopMatInfo.supportS8S8S32); MNN_ASSERT(coopMatInfo.selectedS8CoopMatShape.size() == 3); mCoopM = coopMatInfo.selectedS8CoopMatShape[0]; mCoopN = coopMatInfo.selectedS8CoopMatShape[1]; mCoopK = coopMatInfo.selectedS8CoopMatShape[2]; uint32_t subgroupSize = backend->getDevice().getSubgroupSize(); MNN_ASSERT(subgroupSize > 0); mSubgroupSize = subgroupSize; _init(biasPtr, true); } VulkanConv1x1CoopA8::VulkanConv1x1CoopA8(VulkanBackend* backend, const Convolution2DCommon* convOption, int ci, int co, uint32_t coopM, uint32_t coopN, uint32_t coopK, uint32_t subgroupSize, std::shared_ptr quantInfo, bool initStaticResource) : VulkanBasicExecution(backend), mCommon(convOption), mCi(ci), mCo(co), mQuantCommon(std::move(quantInfo)) { mCoopM = coopM; mCoopN = coopN; mCoopK = coopK; MNN_ASSERT(subgroupSize > 0); mSubgroupSize = subgroupSize; _init(nullptr, initStaticResource); } VulkanConv1x1CoopA8::~VulkanConv1x1CoopA8() { } bool VulkanConv1x1CoopA8::onClone(Backend* bn, const Op* op, VulkanBasicExecution** dst) { if (nullptr == dst) { return true; } auto vkBn = static_cast(bn); auto conv2D = op->main_as_Convolution2D(); if (nullptr == conv2D || nullptr == conv2D->common()) { return false; } auto res = new VulkanConv1x1CoopA8(vkBn, conv2D->common(), mCi, mCo, mCoopM, mCoopN, mCoopK, mSubgroupSize, mQuantCommon, false); res->mPadK = mPadK; res->mPadN = mPadN; res->mBiasBuffer = mBiasBuffer; res->mQuantWeightBuffer = mQuantWeightBuffer; res->mQuantMetaBuffer = mQuantMetaBuffer; res->mSumWqBuffer = mSumWqBuffer; *dst = res; return true; } bool VulkanConv1x1CoopA8::_init(const float* biasPtr, bool initStaticResource) { auto vkBn = static_cast(backend()); const bool useFP16 = vkBn->useFP16(); const size_t fpElem = useFP16 ? sizeof(int16_t) : sizeof(float); mIsInt4 = mQuantCommon != nullptr && mQuantCommon->canUseInt4; const uint32_t K = mCi; const uint32_t N = mCo; mPadK = ROUND_UP(K, mCoopK); mPadN = ROUND_UP(N, mCoopN); if (initStaticResource) { mBiasBuffer = std::make_shared(vkBn->getMemoryPool(), false, fpElem * mPadN, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); auto biasMap = mBiasBuffer->map(); ::memset(biasMap, 0, mPadN * fpElem); if (biasPtr) { if (useFP16) { std::vector biasFP16(N); FLOAT_TO_HALF(biasPtr, biasFP16.data(), N); ::memcpy(biasMap, biasFP16.data(), N * sizeof(int16_t)); } else { ::memcpy(biasMap, biasPtr, N * sizeof(float)); } } mBiasBuffer->unmap(); if (!_prepareStaticBuffersGPU(vkBn, mQuantCommon.get(), useFP16, mCi, mCo, mPadN, mPadK, mQuantWeightBuffer, mQuantMetaBuffer, mSumWqBuffer)) { return false; } } int activation = 0; if (mCommon->relu()) { activation = 1; } else if (mCommon->relu6()) { activation = 2; } // Decode (M == 1): reuse Coop-A16's gemv_dequant_int{4,8} — per-channel is // the single-block subset of block-quant. INT4 / INT8 share the binding // layout and push-constant struct; only weightStride and shader name vary // (handled in onEncode). { std::vector types(5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {mSubgroupSize, 1, 1}; std::vector spec = {(uint32_t)activation}; const char* shader; if (mIsInt4) { shader = useFP16 ? "glsl_gemv_dequant_int4_FP16_comp" : "glsl_gemv_dequant_int4_comp"; } else { shader = useFP16 ? "glsl_gemv_dequant_int8_FP16_comp" : "glsl_gemv_dequant_int8_comp"; } mDecodePipeline = vkBn->getPipeline(shader, types, localSize, spec); mDecodeSet.reset(mDecodePipeline->createSet()); } // INT4-only: runtime nibble unpack pipeline. Idempotent across INT8 mode // (skipped on dispatch side); registered here only when needed. if (mIsInt4) { std::vector types(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {16u, 16u, 1u}; mInt4UnpackPipeline = vkBn->getPipeline("glsl_dynamic_int4_to_int8_unpack_comp", types, localSize, {}); mInt4UnpackSet.reset(mInt4UnpackPipeline->createSet()); } // Prefill stages — spec constant_id starts at 3 (after local_size_x/y/z). { std::vector types(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {mCoopM, 1, 1}; std::vector spec = {mCoopM}; const char* shader = useFP16 ? "glsl_dynamic_quant_minmax_FP16_comp" : "glsl_dynamic_quant_minmax_comp"; mQuantMinMaxPipeline = vkBn->getPipeline(shader, types, localSize, spec); mQuantMinMaxSet.reset(mQuantMinMaxPipeline->createSet()); } { std::vector types(4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {64u, 1u, 1u}; const char* shader = useFP16 ? "glsl_dynamic_quant_reduce_minmax_FP16_comp" : "glsl_dynamic_quant_reduce_minmax_comp"; mQuantReduceMinMaxPipeline = vkBn->getPipeline(shader, types, localSize, {}); mQuantReduceMinMaxSet.reset(mQuantReduceMinMaxPipeline->createSet()); } { std::vector types(5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); const uint32_t packLocalX = std::max(mCoopM, mCoopK); std::vector localSize = {packLocalX, 1u, 1u}; std::vector spec = {mCoopM, mCoopK}; const char* shader = useFP16 ? "glsl_dynamic_quant_pack_FP16_comp" : "glsl_dynamic_quant_pack_comp"; mQuantPackPipeline = vkBn->getPipeline(shader, types, localSize, spec); mQuantPackSet.reset(mQuantPackPipeline->createSet()); } { std::vector types(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {64u, 1u, 1u}; mQuantReduceSumPipeline = vkBn->getPipeline("glsl_dynamic_quant_reduce_sum_comp", types, localSize, {}); mQuantReduceSumSet.reset(mQuantReduceSumPipeline->createSet()); } { // GEMM spec constants 3..9: COOP_M, COOP_N, COOP_K, A_COL_MAJOR=0, // B_COL_MAJOR=1, A_BLOCK_LINEAR=1, B_BLOCK_LINEAR=0. std::vector types(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {mSubgroupSize, 1u, 1u}; std::vector spec = {mCoopM, mCoopN, mCoopK, 0u, 1u, 1u, 0u}; mGemmS8Pipeline = vkBn->getPipeline("glsl_dynamic_w8a8_coop_gemm_comp", types, localSize, spec); mGemmSet.reset(mGemmS8Pipeline->createSet()); } { std::vector types(8, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); std::vector localSize = {16u, 16u, 1u}; std::vector spec = {(uint32_t)activation}; const char* shader = useFP16 ? "glsl_dynamic_w8a8_dequant_correction_FP16_comp" : "glsl_dynamic_w8a8_dequant_correction_comp"; mDequantPipeline = vkBn->getPipeline(shader, types, localSize, spec); mDequantSet.reset(mDequantPipeline->createSet()); } return true; } ErrorCode VulkanConv1x1CoopA8::onEncode(const std::vector& inputs, const std::vector& outputs, const VulkanCommandPool::Buffer* cmdBuffer) { auto input = inputs[0]; auto output = outputs[0]; auto vkBn = static_cast(backend()); const int M = input->batch() * input->width() * input->height(); const int K = mCi; const int N = mCo; const uint32_t padK = mPadK; const uint32_t padN = mPadN; auto srcBuffer = vkBn->getTensorBuffer(input); auto dstBuffer = vkBn->getTensorBuffer(output); const bool useFP16 = vkBn->useFP16(); // Per-shader timing scope (no-op when ENABLE_VULKAN_TIME_PROFILE is off). // The label MUST match the shader registry key passed to getPipeline so // the profile output and the shader registry are 1:1 traceable. auto dispatchWithProfile = [&](const char* name, const VulkanPipeline* pipeline, const std::shared_ptr& set, uint32_t gx, uint32_t gy, uint32_t gz, const void* pc, uint32_t pcSize) { #ifdef ENABLE_VULKAN_TIME_PROFILE auto* profiler = vkBn->timeProfiler(); if (nullptr != profiler) { VulkanTimeProfileScope scope(profiler, cmdBuffer->get(), name, VulkanTimeProfiler::Kind::Shader); pipeline->bind(cmdBuffer->get(), set->get()); if (pc != nullptr) { vkCmdPushConstants(cmdBuffer->get(), pipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, pcSize, pc); } vkCmdDispatch(cmdBuffer->get(), gx, gy, gz); return; } #else (void)name; #endif pipeline->bind(cmdBuffer->get(), set->get()); if (pc != nullptr) { vkCmdPushConstants(cmdBuffer->get(), pipeline->layout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, pcSize, pc); } vkCmdDispatch(cmdBuffer->get(), gx, gy, gz); }; if (M == 1) { struct DecodeParams { uint32_t K; uint32_t N; uint32_t blockSize; uint32_t blockStride; uint32_t weightStride; } pc; pc.K = (uint32_t)K; pc.N = (uint32_t)N; pc.blockSize = (uint32_t)K; pc.blockStride = 1u; pc.weightStride = mIsInt4 ? (padK / 8u) : (padK / 4u); mDecodeSet->writeBuffer(srcBuffer.first->buffer(), 0, vkBn->getTensorSize(input), srcBuffer.second); mDecodeSet->writeBuffer(mQuantWeightBuffer->buffer(), 1, mQuantWeightBuffer->size()); mDecodeSet->writeBuffer(mQuantMetaBuffer->buffer(), 2, mQuantMetaBuffer->size()); mDecodeSet->writeBuffer(mBiasBuffer->buffer(), 3, mBiasBuffer->size()); mDecodeSet->writeBuffer(dstBuffer.first->buffer(), 4, vkBn->getTensorSize(output), dstBuffer.second); const char* decodeName; if (mIsInt4) { decodeName = useFP16 ? "glsl_gemv_dequant_int4_FP16_comp" : "glsl_gemv_dequant_int4_comp"; } else { decodeName = useFP16 ? "glsl_gemv_dequant_int8_FP16_comp" : "glsl_gemv_dequant_int8_comp"; } dispatchWithProfile(decodeName, mDecodePipeline, mDecodeSet, (uint32_t)N, 1, 1, &pc, sizeof(pc)); return NO_ERROR; } const uint32_t padM = ROUND_UP(M, mCoopM); const uint32_t k4Count = UP_DIV((uint32_t)K, 4u); // 64 below mirrors K4_BLOCK in dynamic_quant_minmax.comp — keep in sync. const uint32_t partialBlocks = std::max(1u, UP_DIV(k4Count, 64u)); const uint32_t tilesK = padK / mCoopK; const halide_type_t fpType = useFP16 ? halide_type_of() : halide_type_of(); std::shared_ptr tPartialMin(Tensor::createDevice({(int)partialBlocks, (int)padM}, fpType)); std::shared_ptr tPartialMax(Tensor::createDevice({(int)partialBlocks, (int)padM}, fpType)); std::shared_ptr tScaleA(Tensor::createDevice({(int)padM}, fpType)); std::shared_ptr tOffsetA(Tensor::createDevice({(int)padM}, fpType)); std::shared_ptr tAq(Tensor::createDevice({(int)padM, (int)padK})); std::shared_ptr tPartialSumAq(Tensor::createDevice({(int)tilesK, (int)padM})); std::shared_ptr tSumAq(Tensor::createDevice({(int)padM})); std::shared_ptr tAcc(Tensor::createDevice({(int)padM, (int)padN})); // INT4 only: unpacked int8 weight buffer that the GEMM reads in place of // mQuantWeightBuffer. Same lifetime as the other DYNAMIC tensors. std::shared_ptr tWqInt8; if (mIsInt4) { tWqInt8.reset(Tensor::createDevice({(int)padN, (int)padK})); } std::vector dyns = {tPartialMin.get(), tPartialMax.get(), tScaleA.get(), tOffsetA.get(), tAq.get(), tPartialSumAq.get(), tSumAq.get(), tAcc.get()}; if (mIsInt4) { dyns.push_back(tWqInt8.get()); } size_t acquired = 0; for (Tensor* t : dyns) { if (!vkBn->onAcquireBuffer(t, Backend::DYNAMIC)) { for (size_t j = 0; j < acquired; ++j) { vkBn->onReleaseBuffer(dyns[j], Backend::DYNAMIC); } return OUT_OF_MEMORY; } ++acquired; } auto bPartialMin = vkBn->getTensorBuffer(tPartialMin.get()); auto bPartialMax = vkBn->getTensorBuffer(tPartialMax.get()); auto bScaleA = vkBn->getTensorBuffer(tScaleA.get()); auto bOffsetA = vkBn->getTensorBuffer(tOffsetA.get()); auto bAq = vkBn->getTensorBuffer(tAq.get()); auto bPartialSum = vkBn->getTensorBuffer(tPartialSumAq.get()); auto bSumAq = vkBn->getTensorBuffer(tSumAq.get()); auto bAcc = vkBn->getTensorBuffer(tAcc.get()); const size_t szPartialMin = vkBn->getTensorSize(tPartialMin.get()); const size_t szPartialMax = vkBn->getTensorSize(tPartialMax.get()); const size_t szScaleA = vkBn->getTensorSize(tScaleA.get()); const size_t szOffsetA = vkBn->getTensorSize(tOffsetA.get()); const size_t szAq = vkBn->getTensorSize(tAq.get()); const size_t szPartialSum = vkBn->getTensorSize(tPartialSumAq.get()); const size_t szSumAq = vkBn->getTensorSize(tSumAq.get()); const size_t szAcc = vkBn->getTensorSize(tAcc.get()); { struct PC { uint32_t M, K, padM, partialBlocks; } pc; pc.M = (uint32_t)M; pc.K = (uint32_t)K; pc.padM = padM; pc.partialBlocks = partialBlocks; mQuantMinMaxSet->writeBuffer(srcBuffer.first->buffer(), 0, vkBn->getTensorSize(input), srcBuffer.second); mQuantMinMaxSet->writeBuffer(bPartialMin.first->buffer(), 1, szPartialMin, bPartialMin.second); mQuantMinMaxSet->writeBuffer(bPartialMax.first->buffer(), 2, szPartialMax, bPartialMax.second); dispatchWithProfile(useFP16 ? "glsl_dynamic_quant_minmax_FP16_comp" : "glsl_dynamic_quant_minmax_comp", mQuantMinMaxPipeline, mQuantMinMaxSet, padM / mCoopM, partialBlocks, 1, &pc, sizeof(pc)); cmdBuffer->barrierSource(bPartialMin.first->buffer(), bPartialMin.second, szPartialMin); cmdBuffer->barrierSource(bPartialMax.first->buffer(), bPartialMax.second, szPartialMax); } { struct PC { uint32_t M, padM, partialBlocks, reserved; } pc; pc.M = (uint32_t)M; pc.padM = padM; pc.partialBlocks = partialBlocks; pc.reserved = 0; mQuantReduceMinMaxSet->writeBuffer(bPartialMin.first->buffer(), 0, szPartialMin, bPartialMin.second); mQuantReduceMinMaxSet->writeBuffer(bPartialMax.first->buffer(), 1, szPartialMax, bPartialMax.second); mQuantReduceMinMaxSet->writeBuffer(bScaleA.first->buffer(), 2, szScaleA, bScaleA.second); mQuantReduceMinMaxSet->writeBuffer(bOffsetA.first->buffer(), 3, szOffsetA, bOffsetA.second); dispatchWithProfile(useFP16 ? "glsl_dynamic_quant_reduce_minmax_FP16_comp" : "glsl_dynamic_quant_reduce_minmax_comp", mQuantReduceMinMaxPipeline, mQuantReduceMinMaxSet, UP_DIV(padM, 64u), 1, 1, &pc, sizeof(pc)); cmdBuffer->barrierSource(bScaleA.first->buffer(), bScaleA.second, szScaleA); cmdBuffer->barrierSource(bOffsetA.first->buffer(), bOffsetA.second, szOffsetA); } { struct PC { uint32_t M, K, padM, padK; } pc; pc.M = (uint32_t)M; pc.K = (uint32_t)K; pc.padM = padM; pc.padK = padK; mQuantPackSet->writeBuffer(srcBuffer.first->buffer(), 0, vkBn->getTensorSize(input), srcBuffer.second); mQuantPackSet->writeBuffer(bScaleA.first->buffer(), 1, szScaleA, bScaleA.second); mQuantPackSet->writeBuffer(bOffsetA.first->buffer(), 2, szOffsetA, bOffsetA.second); mQuantPackSet->writeBuffer(bAq.first->buffer(), 3, szAq, bAq.second); mQuantPackSet->writeBuffer(bPartialSum.first->buffer(), 4, szPartialSum, bPartialSum.second); dispatchWithProfile(useFP16 ? "glsl_dynamic_quant_pack_FP16_comp" : "glsl_dynamic_quant_pack_comp", mQuantPackPipeline, mQuantPackSet, padM / mCoopM, tilesK, 1, &pc, sizeof(pc)); cmdBuffer->barrierSource(bAq.first->buffer(), bAq.second, szAq); cmdBuffer->barrierSource(bPartialSum.first->buffer(), bPartialSum.second, szPartialSum); } { struct PC { uint32_t M, padM, tilesK, reserved; } pc; pc.M = (uint32_t)M; pc.padM = padM; pc.tilesK = tilesK; pc.reserved = 0; mQuantReduceSumSet->writeBuffer(bPartialSum.first->buffer(), 0, szPartialSum, bPartialSum.second); mQuantReduceSumSet->writeBuffer(bSumAq.first->buffer(), 1, szSumAq, bSumAq.second); dispatchWithProfile("glsl_dynamic_quant_reduce_sum_comp", mQuantReduceSumPipeline, mQuantReduceSumSet, UP_DIV(padM, 64u), 1, 1, &pc, sizeof(pc)); cmdBuffer->barrierSource(bSumAq.first->buffer(), bSumAq.second, szSumAq); } // INT4 only: nibble-packed weight -> int8 [padN, padK] before GEMM. The // O(padN*padK) write/read here trades INT4 runtime bandwidth back to INT8 // levels, in exchange for fully reusing the W8A8 GEMM body. if (mIsInt4) { struct PC { uint32_t padN, padK, halfK; } pc; pc.padN = padN; pc.padK = padK; pc.halfK = padK / 2u; auto bWqInt8 = vkBn->getTensorBuffer(tWqInt8.get()); const size_t szWqInt8 = vkBn->getTensorSize(tWqInt8.get()); mInt4UnpackSet->writeBuffer(mQuantWeightBuffer->buffer(), 0, mQuantWeightBuffer->size()); mInt4UnpackSet->writeBuffer(bWqInt8.first->buffer(), 1, szWqInt8, bWqInt8.second); dispatchWithProfile("glsl_dynamic_int4_to_int8_unpack_comp", mInt4UnpackPipeline, mInt4UnpackSet, UP_DIV(pc.halfK, 16u), UP_DIV(padN, 16u), 1, &pc, sizeof(pc)); cmdBuffer->barrierSource(bWqInt8.first->buffer(), bWqInt8.second, szWqInt8); } { struct PC { uint32_t M, N, K; } pc; pc.M = padM; pc.N = padN; pc.K = padK; mGemmSet->writeBuffer(bAq.first->buffer(), 0, szAq, bAq.second); if (mIsInt4) { auto bWqInt8 = vkBn->getTensorBuffer(tWqInt8.get()); const size_t szWqInt8 = vkBn->getTensorSize(tWqInt8.get()); mGemmSet->writeBuffer(bWqInt8.first->buffer(), 1, szWqInt8, bWqInt8.second); } else { mGemmSet->writeBuffer(mQuantWeightBuffer->buffer(), 1, mQuantWeightBuffer->size()); } mGemmSet->writeBuffer(bAcc.first->buffer(), 2, szAcc, bAcc.second); dispatchWithProfile("glsl_dynamic_w8a8_coop_gemm_comp", mGemmS8Pipeline, mGemmSet, padN / mCoopN, padM / mCoopM, 1, &pc, sizeof(pc)); cmdBuffer->barrierSource(bAcc.first->buffer(), bAcc.second, szAcc); } { struct PC { uint32_t M, N, K, padM, padN; } pc; pc.M = (uint32_t)M; pc.N = (uint32_t)N; pc.K = (uint32_t)K; pc.padM = padM; pc.padN = padN; const uint32_t n4_valid = UP_DIV((uint32_t)N, 4u); mDequantSet->writeBuffer(bAcc.first->buffer(), 0, szAcc, bAcc.second); mDequantSet->writeBuffer(bScaleA.first->buffer(), 1, szScaleA, bScaleA.second); mDequantSet->writeBuffer(bOffsetA.first->buffer(), 2, szOffsetA, bOffsetA.second); mDequantSet->writeBuffer(bSumAq.first->buffer(), 3, szSumAq, bSumAq.second); mDequantSet->writeBuffer(mQuantMetaBuffer->buffer(), 4, mQuantMetaBuffer->size()); mDequantSet->writeBuffer(mSumWqBuffer->buffer(), 5, mSumWqBuffer->size()); mDequantSet->writeBuffer(mBiasBuffer->buffer(), 6, mBiasBuffer->size()); mDequantSet->writeBuffer(dstBuffer.first->buffer(), 7, vkBn->getTensorSize(output), dstBuffer.second); dispatchWithProfile(useFP16 ? "glsl_dynamic_w8a8_dequant_correction_FP16_comp" : "glsl_dynamic_w8a8_dequant_correction_comp", mDequantPipeline, mDequantSet, UP_DIV((uint32_t)M, 16u), UP_DIV(n4_valid, 16u), 1, &pc, sizeof(pc)); } for (Tensor* t : dyns) { vkBn->onReleaseBuffer(t, Backend::DYNAMIC); } return NO_ERROR; } } // namespace MNN