// // VulkanMatMul.cpp // MNN // // Created by MNN on 2020/03/06. // Copyright © 2018, Alibaba Group Holding Limited // #include "VulkanMatMul.hpp" #include "core/TensorUtils.hpp" namespace MNN { class TexturePosComputer { public: TexturePosComputer(const Tensor* tensor) { mFormat = TensorUtils::getDescribe(tensor)->dimensionFormat; mNHWC = VulkanTensor::tensorShapeFormat(tensor); } ~ TexturePosComputer() { // Do nothing } // X, Y, rgba std::array computePos(int offset) const { std::array nhwcPos; if (mFormat == MNN_DATA_FORMAT_NHWC) { // NHWC nhwcPos[3] = offset % mNHWC[3]; offset = (offset - nhwcPos[3]) / mNHWC[3]; nhwcPos[2] = offset % mNHWC[2]; offset = (offset - nhwcPos[2]) / mNHWC[2]; nhwcPos[1] = offset % mNHWC[1]; nhwcPos[0] = offset / mNHWC[1]; } else { // NCHW nhwcPos[2] = offset % mNHWC[2]; offset = offset / mNHWC[2]; nhwcPos[1] = offset % mNHWC[1]; offset = offset / mNHWC[1]; nhwcPos[3] = offset % mNHWC[3]; nhwcPos[0] = offset / mNHWC[3]; } // MNN_PRINT("n, c, h, w: %d, %d, %d, %d\n", nhwcPos[0], nhwcPos[3], nhwcPos[1], nhwcPos[2]); std::array res; res[2] = nhwcPos[3] % 4; auto c4 = nhwcPos[3] / 4; res[0] = c4 * mNHWC[2] + nhwcPos[2]; res[1] = nhwcPos[0] * mNHWC[1] + nhwcPos[1]; return res; } private: MNN_DATA_FORMAT mFormat; std::array mNHWC; }; VulkanMatMul::Reorder::Reorder(const VulkanBackend* bn, bool transpose, bool revert) { std::vector types{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }; if (revert) { mFirst = bn->getPipeline("glsl_nc4hw4Tonchw_comp", types); } else { mFirst = bn->getPipeline("glsl_nchwTonc4hw4_comp", types); } mBufferBufferSet.reset(mFirst->createSet()); mBackend = bn; mUnitBuffer.reset(new VulkanBuffer(bn->getMemoryPool(), false, sizeof(nchwBuffer), nullptr, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); std::string imageShaderName = "glsl_packAsImage4x4"; std::vector secondTypes{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }; if (revert) { secondTypes[0] = VK_DESCRIPTOR_TYPE_SAMPLER; imageShaderName = "glsl_unPackImage4x4"; } if (transpose) { imageShaderName = imageShaderName + "_TRANSPOSE_comp"; } else { imageShaderName = imageShaderName + "_comp"; } mSecond = bn->getPipeline(imageShaderName, secondTypes); mImageBufferSet.reset(mSecond->createSet()); } void VulkanMatMul::Reorder::encode(VkBuffer source, size_t sourceSize, VkBuffer middleBuffer, size_t middelBufferSize, const VulkanImage* dest, const VulkanCommandPool::Buffer* cmdBuffer, const VulkanMatMul::Reorder::nchwBuffer& buffer) { // First: nchw to nc4hw4 auto ptr = (nchwBuffer*)mUnitBuffer->map(); ::memcpy(ptr, &buffer, sizeof(buffer)); mUnitBuffer->unmap(); auto c = buffer.size[1]; auto b = buffer.size[0]; auto w = buffer.size[3]; auto h = buffer.size[2]; auto cDiv4 = UP_DIV(c, 4); mBufferBufferSet->writeBuffer(middleBuffer, 1, middelBufferSize); mBufferBufferSet->writeBuffer(source, 0, sourceSize, 0); mBufferBufferSet->writeBuffer(mUnitBuffer->buffer(), 2, mUnitBuffer->size()); auto totalNumber = cDiv4 * w * h * b; mFirst->bind(cmdBuffer->get(), mBufferBufferSet->get()); cmdBuffer->barrierSource(source, 0, sourceSize); vkCmdDispatch(cmdBuffer->get(), UP_DIV(totalNumber, 256), 1, 1); // Second: nc4hw4 to image2d dest->barrierWrite(cmdBuffer->get()); mImageBufferSet->writeImage(dest->view(), mBackend->getCommonSampler()->get(), VK_IMAGE_LAYOUT_GENERAL, 0); mImageBufferSet->writeBuffer(middleBuffer, 1, middelBufferSize); mImageBufferSet->writeBuffer(mUnitBuffer->buffer(), 2, mUnitBuffer->size()); mSecond->bind(cmdBuffer->get(), mImageBufferSet->get()); cmdBuffer->barrierSource(middleBuffer, 0, middelBufferSize); auto totalSchedule = cDiv4 * w * h * UP_DIV(b, 4); vkCmdDispatch(cmdBuffer->get(), UP_DIV(totalSchedule, 256), 1, 1); } int VulkanMatMul::Reorder::computeMiddleBufferSize(int b, int h, int w, int c) const { auto cDiv4 = UP_DIV(c, 4); auto totalNumber = cDiv4 * w * h * b; return totalNumber * 4; } void VulkanMatMul::Reorder::revert(VkBuffer dest, size_t destSize, VkBuffer middleBuffer, size_t middelBufferSize, const VulkanImage* source, const VulkanCommandPool::Buffer* cmdBuffer, const VulkanMatMul::Reorder::nchwBuffer& buffer) { // First: nchw to nc4hw4 auto ptr = (nchwBuffer*)mUnitBuffer->map(); ::memcpy(ptr, &buffer, sizeof(buffer)); mUnitBuffer->unmap(); auto c = buffer.size[1]; auto b = buffer.size[0]; auto w = buffer.size[3]; auto h = buffer.size[2]; auto cDiv4 = UP_DIV(c, 4); // First: image2d to nc4hw4 mImageBufferSet->writeImage(source->view(), mBackend->getCommonSampler()->get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0); mImageBufferSet->writeBuffer(middleBuffer, 1, middelBufferSize); mImageBufferSet->writeBuffer(mUnitBuffer->buffer(), 2, mUnitBuffer->size()); mSecond->bind(cmdBuffer->get(), mImageBufferSet->get()); auto totalSchedule = cDiv4 * w * h * UP_DIV(b, 4); source->barrierRead(cmdBuffer->get()); // cmdBuffer->barrierImage(source->get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); vkCmdDispatch(cmdBuffer->get(), UP_DIV(totalSchedule, 256), 1, 1); // Second: nc4hw4 to nchw mBufferBufferSet->writeBuffer(middleBuffer, 1, middelBufferSize); mBufferBufferSet->writeBuffer(dest, 0, destSize, 0); mBufferBufferSet->writeBuffer(mUnitBuffer->buffer(), 2, mUnitBuffer->size()); auto totalNumber = cDiv4 * w * h * b; mFirst->bind(cmdBuffer->get(), mBufferBufferSet->get()); cmdBuffer->barrierSource(middleBuffer, 0, middelBufferSize); vkCmdDispatch(cmdBuffer->get(), UP_DIV(totalNumber, 256), 1, 1); } VulkanMatMul::VulkanMatMul(bool transposeA, bool transposeB, Backend* bn) : VulkanBasicExecution(bn) { mTransposeA = transposeA; mTransposeB = transposeB; } bool VulkanMatMul::encode(const std::vector &inputs, const std::vector &outputs, const VulkanCommandPool::Buffer *cmdBuffer, const MatMulInfo& info) { mTempBuffer.clear(); mSets.clear(); auto input0T = reinterpret_cast(inputs[0]->deviceId()); auto input1T = reinterpret_cast(inputs[1]->deviceId()); auto outputT = reinterpret_cast(outputs[0]->deviceId()); if (input0T->imageSize() > 1 || input1T->imageSize() > 1 || outputT->imageSize() > 1) { // TODO: Copy to temp buffer return false; } auto types = std::vector{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, }; const VulkanPipeline* mInputPipline = nullptr; const VulkanPipeline* mWeightPipline = nullptr; const VulkanPipeline* mOutputPipeline = nullptr; auto vkBn = static_cast(backend()); int e = info.e; int l = info.l; int h = info.h; struct OffsetBuffer { ivec4 size; float transpose[16]; }; { // Pretreat B // Turn bStride(e, l, h) -> x,y + offset // MNN_PRINT("Compute B %d x %d:\n", inputs[1]->length(0), inputs[1]->length(1)); TexturePosComputer comp(inputs[1]); auto offset0 = comp.computePos(info.offsetB); if (offset0[2] != 0) { return false; } auto offsetl = comp.computePos(info.bStride[1]); auto offseth = comp.computePos(info.bStride[2]); bool transposeB = offsetl[2] == 1; if (l == 1) { // TODO: Find better way transposeB = offseth[2] == 0; } int l_W = offsetl[0] * 4 + offsetl[2]; int l_H = offsetl[1] * 4; int h_W = offseth[0] * 4 + offseth[2]; int h_H = offseth[1] * 4; // MNN_PRINT("Compute B: %d x %d, %d-%d-%d, %d-%d-%d\n", info.bStride[1], info.bStride[2], offsetl[0], offsetl[1], offsetl[2], offseth[0], offseth[1], offseth[2]); if (transposeB) { mWeightPipline = vkBn->getPipeline("glsl_matmul_input_TRANSPOSE_comp", types); } else { mWeightPipline = vkBn->getPipeline("glsl_matmul_input_comp", types); } mKernelImage.reset(new VulkanImage(vkBn->getDynamicMemoryPool(), false, {ALIGN_UP4(l), UP_DIV(h, 4)})); std::shared_ptr des(mWeightPipline->createSet()); OffsetBuffer buffer; buffer.size[0] = UP_DIV(l, 4); buffer.size[1] = UP_DIV(h, 4); if (transposeB) { buffer.size[2] = h; } else { buffer.size[2] = l; } buffer.size[2] = buffer.size[2] + offset0[1]; buffer.size[3] = UP_DIV(l, 4) * UP_DIV(h, 4); ::memset(buffer.transpose, 0, 16 * sizeof(float)); buffer.transpose[4 * 0 + 0] = l_W; buffer.transpose[4 * 0 + 1] = h_W; buffer.transpose[4 * 0 + 2] = 0; buffer.transpose[4 * 0 + 3] = offset0[0]; buffer.transpose[4 * 1 + 0] = l_H; buffer.transpose[4 * 1 + 1] = h_H; buffer.transpose[4 * 1 + 2] = 0; buffer.transpose[4 * 1 + 3] = offset0[1]; std::shared_ptr uniformBuffer(new VulkanBuffer(vkBn->getMemoryPool(), false, sizeof(OffsetBuffer), &buffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); des->writeBuffer(uniformBuffer->buffer(), 2, uniformBuffer->size()); des->writeImage(mKernelImage->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_GENERAL, 0); des->writeImage(input1T->image()->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 1); mTempBuffer.emplace_back(uniformBuffer); mSets.emplace_back(des); mWeightPipline->bind(cmdBuffer->get(), des->get()); input1T->image()->barrierRead(cmdBuffer->get()); mKernelImage->barrierWrite(cmdBuffer->get()); vkCmdDispatch(cmdBuffer->get(), UP_DIV(buffer.size[3], 256), 1, 1); } { // Pretreat A TexturePosComputer comp(inputs[0]); auto offset0 = comp.computePos(info.offsetA); if (offset0[2] != 0) { return false; } auto offsetl = comp.computePos(info.aStride[1]); auto offsete = comp.computePos(info.aStride[0]); int l_W = offsetl[0] * 4 + offsetl[2]; int l_H = offsetl[1] * 4; int e_W = offsete[0] * 4 + offsete[2]; int e_H = offsete[1] * 4; bool transposeA = offsetl[2] != 1; if (l == 1) { // TODO: Find better way transposeA = offsete[2] == 1; } if (!transposeA) { mInputPipline = vkBn->getPipeline("glsl_matmul_input_comp", types); } else { mInputPipline = vkBn->getPipeline("glsl_matmul_input_TRANSPOSE_comp", types); } mInputImage.reset(new VulkanImage(vkBn->getDynamicMemoryPool(), false, {ALIGN_UP4(l), UP_DIV(e, 4)})); std::shared_ptr des(mInputPipline->createSet()); OffsetBuffer buffer; buffer.size[0] = UP_DIV(l, 4); buffer.size[1] = UP_DIV(e, 4); if (transposeA) { buffer.size[2] = l; } else { buffer.size[2] = e; } buffer.size[2] = buffer.size[2] + offset0[1]; buffer.size[3] = UP_DIV(l, 4) * UP_DIV(e, 4); ::memset(buffer.transpose, 0, 16 * sizeof(float)); buffer.transpose[4 * 0 + 0] = l_W; buffer.transpose[4 * 0 + 1] = e_W; buffer.transpose[4 * 0 + 2] = 0; buffer.transpose[4 * 0 + 3] = offset0[0]; buffer.transpose[4 * 1 + 0] = l_H; buffer.transpose[4 * 1 + 1] = e_H; buffer.transpose[4 * 1 + 2] = 0; buffer.transpose[4 * 1 + 3] = offset0[1]; std::shared_ptr uniformBuffer(new VulkanBuffer(vkBn->getMemoryPool(), false, sizeof(OffsetBuffer), &buffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); des->writeBuffer(uniformBuffer->buffer(), 2, uniformBuffer->size()); des->writeImage(mInputImage->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_GENERAL, 0); des->writeImage(input0T->image()->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 1); mTempBuffer.emplace_back(uniformBuffer); mSets.emplace_back(des); mInputPipline->bind(cmdBuffer->get(), des->get()); input0T->image()->barrierRead(cmdBuffer->get()); mInputImage->barrierWrite(cmdBuffer->get()); vkCmdDispatch(cmdBuffer->get(), UP_DIV(buffer.size[3], 256), 1, 1); } mCore.reset(new VulkanMatrixMultier4x4(vkBn, nullptr, l, h, 1, mKernelImage)); mOutputImage.reset(new VulkanImage(vkBn->getDynamicMemoryPool(), false, {ALIGN_UP4(h), UP_DIV(e, 4)})); mCore->prepare(cmdBuffer, e, mOutputImage, mInputImage); mCore->compute(cmdBuffer); mInputImage->release(); mKernelImage->release(); { // Posttreat C if (inputs.size() > 2) { auto ntypes = std::vector{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, }; mOutputPipeline = vkBn->getPipeline("glsl_matmul_output_BIAS_comp", ntypes); } else { mOutputPipeline = vkBn->getPipeline("glsl_matmul_output_comp", types); } TexturePosComputer comp(outputs[0]); auto offset0 = comp.computePos(info.offsetC); if (offset0[2] != 0) { return false; } auto offsete = comp.computePos(info.cStride[0]); auto offseth = comp.computePos(info.cStride[2]); std::shared_ptr des(mOutputPipeline->createSet()); OffsetBuffer buffer; buffer.size[0] = UP_DIV(h, 4); buffer.size[1] = UP_DIV(e, 4); buffer.size[2] = e + offset0[1]; buffer.size[3] = UP_DIV(h, 4) * UP_DIV(e, 4); ::memset(buffer.transpose, 0, 16 * sizeof(float)); buffer.transpose[4 * 0 + 0] = 1; buffer.transpose[4 * 0 + 1] = 0; buffer.transpose[4 * 0 + 2] = 0; buffer.transpose[4 * 0 + 3] = offset0[0]; buffer.transpose[4 * 1 + 0] = 0; buffer.transpose[4 * 1 + 1] = 4; buffer.transpose[4 * 1 + 2] = 0; buffer.transpose[4 * 1 + 3] = offset0[1]; std::shared_ptr uniformBuffer(new VulkanBuffer(vkBn->getMemoryPool(), false, sizeof(OffsetBuffer), &buffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); des->writeBuffer(uniformBuffer->buffer(), 2, uniformBuffer->size()); des->writeImage(outputT->image()->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_GENERAL, 0); des->writeImage(mOutputImage->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 1); if (inputs.size() > 2) { float biasTranspose[16]; std::shared_ptr biasuniformBuffer(new VulkanBuffer(vkBn->getMemoryPool(), false, sizeof(biasTranspose), &biasTranspose, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); des->writeImage(reinterpret_cast(inputs[2]->deviceId())->image()->view(), vkBn->getCommonSampler()->get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 3); des->writeBuffer(biasuniformBuffer->buffer(), 4, 16 * sizeof(float)); mTempBuffer.emplace_back(biasuniformBuffer); } mSets.emplace_back(des); mOutputPipeline->bind(cmdBuffer->get(), des->get()); outputT->image()->barrierWrite(cmdBuffer->get()); mOutputImage->barrierRead(cmdBuffer->get()); vkCmdDispatch(cmdBuffer->get(), UP_DIV(buffer.size[3], 256), 1, 1); mTempBuffer.emplace_back(uniformBuffer); } mOutputImage->release(); return true; } ErrorCode VulkanMatMul::onEncode(const std::vector &inputs, const std::vector &outputs, const VulkanCommandPool::Buffer *cmdBuffer) { Tensor* C = outputs[0]; auto w0 = inputs[0]->length(1); auto h0 = inputs[0]->length(0); auto e = C->length(0); auto h = C->length(1); auto l = w0; if (mTransposeA) { l = h0; } MatMulInfo info; info.e = e; info.l = l; info.h = h; if (mTransposeA) { info.aStride[0] = 1; info.aStride[1] = e; info.aStride[2] = 0; } else { info.aStride[0] = l; info.aStride[1] = 1; info.aStride[2] = 0; } if (mTransposeB) { info.bStride[0] = 0; info.bStride[1] = 1; info.bStride[2] = l; } else { info.bStride[0] = 0; info.bStride[1] = h; info.bStride[2] = 1; } info.cStride[0] = h; info.cStride[1] = 0; info.cStride[2] = 1; auto res = encode(inputs, outputs, cmdBuffer, info); if (!res) { return NOT_SUPPORT; } return NO_ERROR; } class VulkanMatMulCreator : public VulkanBackend::Creator { public: virtual VulkanBasicExecution* onCreate(const std::vector& inputs, const std::vector& outputs, const MNN::Op* op, Backend* bn) const override { Tensor* C = outputs[0]; auto w0 = inputs[0]->length(1); auto h0 = inputs[0]->length(0); auto e = C->length(0); auto h = C->length(1); auto l = w0; auto vkBn = static_cast(bn); const auto mat = op->main_as_MatMul(); if (mat->transposeA()) { l = h0; } auto limit = vkBn->proty().limits.maxImageDimension2D; if (e > limit || h > limit || l > limit) { return nullptr; } return new VulkanMatMul(mat->transposeA(), mat->transposeB(), bn); } }; static bool gResistor = []() { VulkanBackend::addCreator(OpType_MatMul, new VulkanMatMulCreator); return true; }(); }