865 lines
32 KiB
Plaintext
865 lines
32 KiB
Plaintext
//
|
|
// MetalLoop.mm
|
|
// MNN
|
|
//
|
|
// Created by MNN on 2023/12/28.
|
|
// Copyright © 2018, Alibaba Group Holding Limited
|
|
//
|
|
|
|
#import "core/Macro.h"
|
|
#import "MetalCast.hpp"
|
|
#import "MetalBinary.hpp"
|
|
#import "MetalBackend.hpp"
|
|
#import "MNNMetalContext.h"
|
|
#include "MNN_generated.h"
|
|
|
|
#if MNN_METAL_ENABLED
|
|
namespace MNN {
|
|
static const char* gMatMulUnitTemplate = R"metal(
|
|
#include <metal_stdlib>
|
|
#include <simd/simd.h>
|
|
|
|
using namespace metal;
|
|
|
|
struct constBuffer
|
|
{
|
|
int4 size;
|
|
int4 stride_o;
|
|
int4 stride_a;
|
|
int4 stride_b;
|
|
int4 stride_c;
|
|
int4 _step;
|
|
int4 iter;
|
|
};
|
|
|
|
kernel void loop_matmul(device T* uOutput [[buffer(0)]], const device T* uInputA [[buffer(1)]], const device T* uInputB [[buffer(2)]],
|
|
#ifdef HAS_BIAS
|
|
const device T* uInputC [[buffer(3)]],
|
|
const device int* uOOffset [[buffer(4)]],
|
|
const device int* uAOffset [[buffer(5)]],
|
|
const device int* uBOffset [[buffer(6)]],
|
|
const device int* uCOffset [[buffer(7)]],
|
|
constant constBuffer& uConstant [[buffer(8)]],
|
|
#else
|
|
const device int* uOOffset [[buffer(3)]],
|
|
const device int* uAOffset [[buffer(4)]],
|
|
const device int* uBOffset [[buffer(5)]],
|
|
constant constBuffer& uConstant [[buffer(6)]],
|
|
#endif
|
|
uint3 gl_GlobalInvocationID [[thread_position_in_grid]])
|
|
{
|
|
int e = uConstant.size.x;
|
|
int l = uConstant.size.y;
|
|
int h = uConstant.size.z;
|
|
int n = uConstant.size.w;
|
|
|
|
int X0 = gl_GlobalInvocationID.x * 4;
|
|
int Y0 = gl_GlobalInvocationID.y * 4;
|
|
int regionOutsideIndex = gl_GlobalInvocationID.z;
|
|
|
|
if (X0 >= e || Y0 >= h || regionOutsideIndex >= n) {
|
|
return;
|
|
}
|
|
|
|
int4 index = int4(regionOutsideIndex, regionOutsideIndex, regionOutsideIndex, regionOutsideIndex);
|
|
if (uConstant.iter.x >= 0) {
|
|
index.x = uOOffset[regionOutsideIndex];
|
|
}
|
|
if (uConstant.iter.y >= 0) {
|
|
index.y = uAOffset[regionOutsideIndex];
|
|
}
|
|
if (uConstant.iter.z >= 0) {
|
|
index.z = uBOffset[regionOutsideIndex];
|
|
}
|
|
#ifdef HAS_BIAS
|
|
if (uConstant.iter.w >= 0) {
|
|
index.w = uCOffset[regionOutsideIndex];
|
|
}
|
|
#endif
|
|
|
|
int4 offset = index * uConstant._step;
|
|
|
|
T value[4][4];
|
|
for (int y = 0; y < 4; ++y) {
|
|
for (int x = 0; x < 4; ++x) {
|
|
value[x][y] = T(0.0);
|
|
}
|
|
}
|
|
|
|
int aOffset0 = offset.y + uConstant.stride_a.w;
|
|
int bOffset0 = offset.z + uConstant.stride_b.w;
|
|
|
|
int a_idx[4];
|
|
int b_idx[4];
|
|
for (int x = 0; x < 4; ++x) {
|
|
a_idx[x] = min(X0 + x, e - 1) * uConstant.stride_a.x;
|
|
}
|
|
for (int y = 0; y < 4; ++y) {
|
|
b_idx[y] = min(Y0 + y, h - 1) * uConstant.stride_b.z;
|
|
}
|
|
|
|
bool safe = (X0 + 3 < e) && (Y0 + 3 < h);
|
|
|
|
if (safe) {
|
|
for (int i = 0; i < l; i++) {
|
|
T a[4];
|
|
T b[4];
|
|
int a_base = aOffset0 + i * uConstant.stride_a.y;
|
|
int b_base = bOffset0 + i * uConstant.stride_b.y;
|
|
|
|
for(int x = 0; x < 4; ++x) {
|
|
a[x] = uInputA[a_base + a_idx[x]];
|
|
}
|
|
|
|
for(int y = 0; y < 4; ++y) {
|
|
b[y] = uInputB[b_base + b_idx[y]];
|
|
}
|
|
|
|
for(int y = 0; y < 4; ++y) {
|
|
for(int x = 0; x < 4; ++x) {
|
|
value[x][y] += a[x] * b[y];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (int i = 0; i < l; i++) {
|
|
T a[4];
|
|
T b[4];
|
|
int a_base = aOffset0 + i * uConstant.stride_a.y;
|
|
int b_base = bOffset0 + i * uConstant.stride_b.y;
|
|
|
|
// Load A with boundary check
|
|
for(int x = 0; x < 4; ++x) {
|
|
if (X0 + x < e) {
|
|
a[x] = uInputA[a_base + a_idx[x]];
|
|
} else {
|
|
a[x] = T(0.0);
|
|
}
|
|
}
|
|
|
|
// Load B with boundary check
|
|
for(int y = 0; y < 4; ++y) {
|
|
if (Y0 + y < h) {
|
|
b[y] = uInputB[b_base + b_idx[y]];
|
|
} else {
|
|
b[y] = T(0.0);
|
|
}
|
|
}
|
|
|
|
for(int y = 0; y < 4; ++y) {
|
|
for(int x = 0; x < 4; ++x) {
|
|
value[x][y] += a[x] * b[y];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < 4; ++y) {
|
|
if (Y0 + y >= h) continue;
|
|
for (int x = 0; x < 4; ++x) {
|
|
if (X0 + x >= e) continue;
|
|
T outVal = value[x][y];
|
|
#ifdef HAS_BIAS
|
|
outVal += uInputC[offset.w + (Y0 + y) * uConstant.stride_c.z + (X0 + x) * uConstant.stride_c.x];
|
|
#endif
|
|
uOutput[offset.x + uConstant.stride_o.w + (X0 + x) * uConstant.stride_o.x + (Y0 + y) * uConstant.stride_o.z] = outVal;
|
|
}
|
|
}
|
|
}
|
|
)metal";
|
|
|
|
struct VulkanBatchMatMulInfo {
|
|
int size[4];
|
|
int stride_o[4];
|
|
int stride_a[4];
|
|
int stride_b[4];
|
|
int stride_c[4];
|
|
int step[4];
|
|
int iter[4];
|
|
};
|
|
static void _setTensorStack(std::vector<Tensor*>& result, const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const LoopParam* loop) {
|
|
if (loop->inputIndexes() != nullptr) {
|
|
for (int i=0; i<loop->inputIndexes()->size(); ++i) {
|
|
result[loop->inputIndexes()->data()[i]] = inputs[i];
|
|
}
|
|
}
|
|
for (int i=0; i<loop->outputIndexes()->size(); ++i) {
|
|
result[loop->outputIndexes()->data()[i]] = outputs[i];
|
|
}
|
|
}
|
|
|
|
class MetalBatchMatMul : public MetalExecution {
|
|
private:
|
|
const LoopParam* mLoop;
|
|
id<MTLBuffer> mParam;
|
|
id<MTLComputePipelineState> mPipeline;
|
|
std::vector<Tensor*> mTensors;
|
|
bool mHasBias = false;
|
|
int mCmdIndex = 0;
|
|
|
|
public:
|
|
MetalBatchMatMul(const LoopParam* loop, Backend *bn, int index = 0) : MetalExecution(bn) {
|
|
mLoop = loop;
|
|
auto mtbn = static_cast<MetalBackend *>(bn);
|
|
auto context = (__bridge MNNMetalContext *)mtbn->context();
|
|
mParam = [context newDeviceBuffer:sizeof(VulkanBatchMatMulInfo) access:CPUWriteOnly];
|
|
bool useFp16 = mtbn->useFp16InsteadFp32();
|
|
NSString* T = nil;
|
|
if (useFp16) {
|
|
T = @"half";
|
|
} else {
|
|
T = @"float";
|
|
}
|
|
std::vector<std::string> keys = {
|
|
std::string([T UTF8String]),
|
|
"matmulunit"
|
|
};
|
|
auto cmd = loop->commands()->GetAs<RegionCommand>(index);
|
|
mHasBias = cmd->indexes()->size() > 3;
|
|
if (mHasBias) {
|
|
keys.emplace_back("BIAS");
|
|
}
|
|
auto pipeline = mtbn->runtime()->findPipeline(keys);
|
|
if (nil == pipeline) {
|
|
MTLCompileOptions *compileOptions = [[MTLCompileOptions alloc] init];
|
|
if (!mHasBias) {
|
|
compileOptions.preprocessorMacros = @{
|
|
@"T" : T,
|
|
};
|
|
} else {
|
|
compileOptions.preprocessorMacros = @{
|
|
@"T" : T,
|
|
@"HAS_BIAS":@"1",
|
|
};
|
|
}
|
|
pipeline = mtbn->makeComputePipelineWithSourceOption(gMatMulUnitTemplate, "loop_matmul", compileOptions);
|
|
mtbn->runtime()->insertPipeline(keys, pipeline);
|
|
|
|
}
|
|
if (nil == pipeline) {
|
|
MNN_ERROR("Create batch matmul pipeline error\n");
|
|
}
|
|
mPipeline = pipeline;
|
|
mTensors.resize(mLoop->tensorNumber());
|
|
mCmdIndex = index;
|
|
}
|
|
virtual ~MetalBatchMatMul() = default;
|
|
virtual ErrorCode onResize(const std::vector<Tensor *>& inputs, const std::vector<Tensor *>& outputs) override {
|
|
_setTensorStack(mTensors, inputs, outputs, mLoop);
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
auto size = cmd->size()->data();
|
|
auto AStride = cmd->view()->GetAs<View>(1)->stride()->data();
|
|
auto BStride = cmd->view()->GetAs<View>(2)->stride()->data();
|
|
auto OStride = cmd->view()->GetAs<View>(0)->stride()->data();
|
|
auto param = reinterpret_cast<VulkanBatchMatMulInfo*>([mParam contents]);
|
|
param->size[3] = mLoop->loopNumber();
|
|
for (int i=0; i<3; ++i) {
|
|
param->size[i] = size[i];
|
|
param->stride_o[i] = OStride[i];
|
|
param->stride_a[i] = AStride[i];
|
|
param->stride_b[i] = BStride[i];
|
|
}
|
|
param->stride_o[3] = cmd->view()->GetAs<View>(0)->offset();
|
|
param->stride_a[3] = cmd->view()->GetAs<View>(1)->offset();
|
|
param->stride_b[3] = cmd->view()->GetAs<View>(2)->offset();
|
|
if (mHasBias) {
|
|
param->stride_c[3] = cmd->view()->GetAs<View>(3)->offset();
|
|
}
|
|
::memcpy(param->step, cmd->steps()->data(), cmd->steps()->size() * sizeof(int));
|
|
::memcpy(param->iter, cmd->iterIndexes()->data(), cmd->iterIndexes()->size() * sizeof(int));
|
|
return NO_ERROR;
|
|
}
|
|
virtual void onEncode(const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &outputs, id<MTLComputeCommandEncoder> encoder) override {
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
auto size = cmd->size()->data();
|
|
auto AStride = cmd->view()->GetAs<View>(1)->stride()->data();
|
|
auto BStride = cmd->view()->GetAs<View>(2)->stride()->data();
|
|
auto OStride = cmd->view()->GetAs<View>(0)->stride()->data();
|
|
[encoder setComputePipelineState:mPipeline];
|
|
for (int i=0; i<cmd->indexes()->size(); ++i) {
|
|
MetalBackend::setTensor(mTensors[cmd->indexes()->data()[i]], encoder, i);
|
|
}
|
|
auto iter = cmd->iterIndexes()->data();
|
|
for (int i=0; i<cmd->indexes()->size(); ++i) {
|
|
if (iter[i] >= 0) {
|
|
MetalBackend::setTensor(mTensors[iter[i]], encoder, cmd->indexes()->size() + i);
|
|
} else {
|
|
MetalBackend::setTensor(inputs[0], encoder, cmd->indexes()->size() + i);
|
|
}
|
|
}
|
|
// printf("loop_matmul out dequant BMNK: %d %d %d %d\n", mLoop->loopNumber(), size[0], size[2], size[1]);
|
|
|
|
[encoder setBuffer:mParam offset:0 atIndex:cmd->indexes()->size() * 2];
|
|
|
|
int e = size[0];
|
|
int h = size[2];
|
|
int n = mLoop->loopNumber();
|
|
int threadsX = UP_DIV(e, 4);
|
|
int threadsY = UP_DIV(h, 4);
|
|
[encoder dispatchThreadgroups:MTLSizeMake(UP_DIV(threadsX, 8), UP_DIV(threadsY, 8), n) threadsPerThreadgroup:MTLSizeMake(8, 8, 1)];
|
|
}
|
|
};
|
|
|
|
static const char* gBlitRegion = R"metal(
|
|
#include <metal_stdlib>
|
|
#include <simd/simd.h>
|
|
using namespace metal;
|
|
struct constBuffer
|
|
{
|
|
int4 stride;
|
|
int4 size;
|
|
int4 extent;
|
|
int4 _step;
|
|
int4 iter;
|
|
int4 totalSize;
|
|
};
|
|
|
|
struct s1
|
|
{
|
|
int data[1];
|
|
};
|
|
|
|
struct s2
|
|
{
|
|
int data[1];
|
|
};
|
|
|
|
struct sourceBuffer
|
|
{
|
|
T data[1];
|
|
};
|
|
|
|
struct s0
|
|
{
|
|
T data[1];
|
|
};
|
|
|
|
kernel void gather_blit(device sourceBuffer& uOutput [[buffer(0)]], const device s0& uInput [[buffer(1)]], const device s1& uSrcOffset [[buffer(2)]], const device s2& uDstOffset [[buffer(3)]], constant constBuffer& uConstant [[buffer(4)]], uint3 gl_GlobalInvocationID [[thread_position_in_grid]])
|
|
{
|
|
int3 posTmp = int3(gl_GlobalInvocationID);
|
|
if (posTmp.x < uConstant._step.w)
|
|
{
|
|
int regionInsideIndex = posTmp.x % uConstant.size.w;
|
|
int regionOutsideIndex = posTmp.x / uConstant.size.w;
|
|
int3 pos;
|
|
pos.x = regionInsideIndex / (uConstant.size.y * uConstant.size.z);
|
|
int subIndex = regionInsideIndex % (uConstant.size.y * uConstant.size.z);
|
|
pos.z = subIndex % uConstant.size.z;
|
|
pos.y = subIndex / uConstant.size.z;
|
|
int srcBasicOffset;
|
|
if (uConstant.iter.y > 0)
|
|
{
|
|
srcBasicOffset = uConstant._step.y * int(uSrcOffset.data[regionOutsideIndex]);
|
|
}
|
|
else
|
|
{
|
|
srcBasicOffset = uConstant._step.y * regionOutsideIndex;
|
|
}
|
|
int dstBasicOffset;
|
|
if (uConstant.iter.x > 0)
|
|
{
|
|
dstBasicOffset = uConstant._step.x * int(uDstOffset.data[regionOutsideIndex]);
|
|
}
|
|
else
|
|
{
|
|
dstBasicOffset = uConstant._step.x * regionOutsideIndex;
|
|
}
|
|
int srcOffset = (((srcBasicOffset + uConstant.stride.w) + (uConstant.stride.z * pos.z)) + (uConstant.stride.y * pos.y)) + (uConstant.stride.x * pos.x);
|
|
int dstOffset = (((dstBasicOffset + uConstant.extent.w) + (pos.x * uConstant.extent.x)) + (pos.y * uConstant.extent.y)) + (pos.z * uConstant.extent.z);
|
|
if(srcOffset >= 0 && srcOffset < uConstant.totalSize.x) {
|
|
if(dstOffset >= 0 && dstOffset < uConstant.totalSize.y) {
|
|
uOutput.data[dstOffset] = uInput.data[srcOffset];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
)metal";
|
|
|
|
struct GatherInfo {
|
|
int stride[4];
|
|
int size[4];
|
|
int extent[4];
|
|
int step[4];
|
|
int iter[4];
|
|
int totalSize[4];
|
|
};
|
|
struct InitInfo {
|
|
int srcStride[4];
|
|
int dstStride[4];
|
|
int size[4];
|
|
int totalSize[4];
|
|
};
|
|
static const char* gInitRegion = R"metal(
|
|
#include <metal_stdlib>
|
|
#include <simd/simd.h>
|
|
using namespace metal;
|
|
|
|
struct constBuffer
|
|
{
|
|
int4 srcStride;
|
|
int4 dstStride;
|
|
int4 size;
|
|
int4 totalSize;
|
|
};
|
|
|
|
kernel void set_zero(device T *out [[buffer(0)]],
|
|
const device T *in [[buffer(1)]],
|
|
constant constBuffer &info [[buffer(2)]],
|
|
uint3 gl_GlobalInvocationID [[thread_position_in_grid]]) {
|
|
int3 gid = int3(gl_GlobalInvocationID);
|
|
if (gid.x >= info.size.x || gid.y >= info.size.y || gid.z >= info.size.z) {
|
|
return;
|
|
}
|
|
int dst_offset = (gid.z * info.size.y + gid.y) * info.size.x + gid.x;
|
|
if(dst_offset >= 0 && dst_offset < info.totalSize.y) {
|
|
out[dst_offset] = (T)0;
|
|
}
|
|
}
|
|
|
|
kernel void set_copy(device T *out [[buffer(0)]],
|
|
const device T *in [[buffer(1)]],
|
|
constant constBuffer &info [[buffer(2)]],
|
|
uint3 gl_GlobalInvocationID [[thread_position_in_grid]]) {
|
|
int3 gid = int3(gl_GlobalInvocationID);
|
|
if (gid.x >= info.size.x || gid.y >= info.size.y || gid.z >= info.size.z) {
|
|
return;
|
|
}
|
|
int src_offset = gid.x * info.srcStride.x + gid.y * info.srcStride.y + gid.z * info.srcStride.z;
|
|
int dst_offset = gid.x * info.dstStride.x + gid.y * info.dstStride.y + gid.z * info.dstStride.z;
|
|
if(src_offset >= 0 && src_offset < info.totalSize.x) {
|
|
if(dst_offset >= 0 && dst_offset < info.totalSize.y) {
|
|
out[dst_offset] = in[src_offset];
|
|
}
|
|
}
|
|
}
|
|
)metal";
|
|
|
|
class MetalGather : public MetalExecution {
|
|
private:
|
|
const LoopParam* mLoop;
|
|
int mCmdIndex = 0;
|
|
id<MTLBuffer> mParam;
|
|
id<MTLComputePipelineState> mPipeline;
|
|
std::vector<Tensor*> mTensors;
|
|
public:
|
|
MetalGather(const LoopParam* loop, Backend *bn, const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, int index = 0) : MetalExecution(bn) {
|
|
mLoop = loop;
|
|
auto mtbn = static_cast<MetalBackend *>(bn);
|
|
auto context = (__bridge MNNMetalContext *)mtbn->context();
|
|
|
|
mParam = [context newDeviceBuffer:sizeof(GatherInfo) access:CPUWriteOnly];
|
|
bool useFp16 = mtbn->useFp16InsteadFp32();
|
|
mTensors.resize(mLoop->tensorNumber());
|
|
mCmdIndex = index;
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
_setTensorStack(mTensors, inputs, outputs, mLoop);
|
|
auto dstTensor = mTensors[cmd->indexes()->data()[0]];
|
|
|
|
NSString* T = MetalCast::getScalarType(dstTensor->getType(), useFp16);
|
|
// gather blit command pipeline
|
|
{
|
|
std::vector<std::string> keys = {
|
|
std::string([T UTF8String]),
|
|
"blitregion"
|
|
};
|
|
auto pipeline = mtbn->runtime()->findPipeline(keys);
|
|
if (nil == pipeline) {
|
|
MTLCompileOptions *compileOptions = [[MTLCompileOptions alloc] init];
|
|
compileOptions.preprocessorMacros = @{
|
|
@"T" : T,
|
|
};
|
|
pipeline = mtbn->makeComputePipelineWithSourceOption(gBlitRegion, "gather_blit", compileOptions);
|
|
mtbn->runtime()->insertPipeline(keys, pipeline);
|
|
}
|
|
if (nil == pipeline) {
|
|
MNN_ERROR("Create gather pipeline error\n");
|
|
}
|
|
mPipeline = pipeline;
|
|
}
|
|
}
|
|
virtual ~MetalGather() = default;
|
|
virtual ErrorCode onResize(const std::vector<Tensor *>& inputs, const std::vector<Tensor *>& outputs) override {
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
_setTensorStack(mTensors, inputs, outputs, mLoop);
|
|
|
|
auto srcStride = cmd->view()->GetAs<View>(1)->stride()->data();
|
|
auto dstStride = cmd->view()->GetAs<View>(0)->stride()->data();
|
|
auto size = cmd->size()->data();
|
|
int totalSize = mLoop->loopNumber() * size[0] * size[1] * size[2];
|
|
auto param = reinterpret_cast<GatherInfo*>([mParam contents]);
|
|
for (int i=0; i<3; ++i) {
|
|
param->size[i] = size[i];
|
|
param->stride[i] = srcStride[i];
|
|
param->extent[i] = dstStride[i];
|
|
}
|
|
param->stride[3] = cmd->view()->GetAs<View>(1)->offset();
|
|
param->extent[3] = cmd->view()->GetAs<View>(0)->offset();
|
|
param->size[3] = size[0] * size[1] * size[2];
|
|
param->step[3] = totalSize;
|
|
param->step[0] = cmd->steps()->data()[0];
|
|
param->step[1] = cmd->steps()->data()[1];
|
|
param->iter[0] = 0;
|
|
param->iter[1] = 0;
|
|
auto iterIndex = cmd->iterIndexes()->data();
|
|
if (iterIndex[0] >= 0) {
|
|
param->iter[0] = 1;
|
|
}
|
|
if (iterIndex[1] >= 0) {
|
|
param->iter[1] = 1;
|
|
}
|
|
|
|
auto dstTensor = mTensors[cmd->indexes()->data()[0]];
|
|
auto srcTensor = mTensors[cmd->indexes()->data()[1]];
|
|
auto inputSize = srcTensor->usize() / srcTensor->buffer().type.bytes();
|
|
auto outputSize = dstTensor->usize() / dstTensor->buffer().type.bytes();
|
|
param->totalSize[0] = inputSize;
|
|
param->totalSize[1] = outputSize;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
virtual void onEncode(const std::vector<Tensor *>& inputs, const std::vector<Tensor *>& outputs,
|
|
id<MTLComputeCommandEncoder> encoder) override {
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
auto size = cmd->size()->data();
|
|
auto srcStride = cmd->view()->GetAs<View>(1)->stride()->data();
|
|
auto dstStride = cmd->view()->GetAs<View>(0)->stride()->data();
|
|
int totalSize = mLoop->loopNumber() * size[0] * size[1] * size[2];
|
|
|
|
[encoder setComputePipelineState:mPipeline];
|
|
auto dstTensor = mTensors[cmd->indexes()->data()[0]];
|
|
auto srcTensor = mTensors[cmd->indexes()->data()[1]];
|
|
MetalBackend::setTensor(dstTensor, encoder, 0);
|
|
MetalBackend::setTensor(srcTensor, encoder, 1);
|
|
|
|
auto iterIndex = cmd->iterIndexes()->data();
|
|
if (iterIndex[0] >= 0) {
|
|
MetalBackend::setTensor(mTensors[iterIndex[0]], encoder, 3);
|
|
} else {
|
|
MetalBackend::setTensor(dstTensor, encoder, 3);
|
|
}
|
|
if (iterIndex[1] >= 0) {
|
|
MetalBackend::setTensor(mTensors[iterIndex[1]], encoder, 2);
|
|
} else {
|
|
MetalBackend::setTensor(srcTensor, encoder, 2);
|
|
}
|
|
[encoder setBuffer:mParam offset:0 atIndex:4];
|
|
[encoder dispatchThreadgroups:MTLSizeMake(UP_DIV(totalSize, 256), 1, 1) threadsPerThreadgroup:MTLSizeMake(256, 1, 1)];
|
|
}
|
|
};
|
|
|
|
|
|
static const char* gBinaryBroadcast = R"metal(
|
|
#include <metal_stdlib>
|
|
#include <simd/simd.h>
|
|
using namespace metal;
|
|
struct constBuffer
|
|
{
|
|
int4 srcview0;
|
|
int4 srcview1;
|
|
int4 dstview;
|
|
int4 size;
|
|
};
|
|
|
|
static inline __attribute__((always_inline))
|
|
int computeVec4dot(thread const int4& a, thread const int4& b)
|
|
{
|
|
return (((a.x * b.x) + (a.y * b.y)) + (a.z * b.z)) + (a.w * b.w);
|
|
}
|
|
|
|
kernel void loop_binary(device T1* uOutput [[buffer(0)]], const device T0* uInput0 [[buffer(1)]], const device T0* uInput1 [[buffer(2)]], constant constBuffer& uConstant [[buffer(3)]], uint3 gl_GlobalInvocationID [[thread_position_in_grid]])
|
|
{
|
|
int3 posTmp = int3(gl_GlobalInvocationID);
|
|
if (posTmp.x < uConstant.size.w)
|
|
{
|
|
int4 pos;
|
|
pos.x = posTmp.x / (uConstant.size.y * uConstant.size.z);
|
|
int subIndex = posTmp.x % (uConstant.size.y * uConstant.size.z);
|
|
pos.z = subIndex % uConstant.size.z;
|
|
pos.y = subIndex / uConstant.size.z;
|
|
pos.w = 1;
|
|
int4 param = uConstant.srcview0;
|
|
int4 param_1 = pos;
|
|
int s0 = computeVec4dot(param, param_1);
|
|
int4 param_2 = uConstant.srcview1;
|
|
int4 param_3 = pos;
|
|
int s1 = computeVec4dot(param_2, param_3);
|
|
int4 param_4 = uConstant.dstview;
|
|
int4 param_5 = pos;
|
|
int d = computeVec4dot(param_4, param_5);
|
|
T0 V0 = uInput0[s0];
|
|
T0 V1 = uInput1[s1];
|
|
uOutput[d] = CUSTOM;
|
|
}
|
|
}
|
|
)metal";
|
|
|
|
struct BinaryBroadCastInfo {
|
|
int srcview0[4];
|
|
int srcview1[4];
|
|
int dstview[4];
|
|
int size[4];
|
|
};
|
|
|
|
class MetalBinaryBroadCast : public MetalExecution {
|
|
public:
|
|
MetalBinaryBroadCast(const LoopParam* loop, Backend *bn, const std::vector<Tensor*>& tensors, NSString* CUSTOM, int index = 0) : MetalExecution(bn) {
|
|
mLoop = loop;
|
|
auto mtbn = static_cast<MetalBackend *>(bn);
|
|
auto context = (__bridge MNNMetalContext *)mtbn->context();
|
|
mParam = mtbn->getConstBuffer(sizeof(BinaryBroadCastInfo));
|
|
mTensors = tensors;
|
|
mCmdIndex = index;
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
auto dstTensor = mTensors[cmd->indexes()->data()[0]];
|
|
auto srcTensor = mTensors[cmd->indexes()->data()[1]];
|
|
auto srcTensor1 = mTensors[cmd->indexes()->data()[2]];
|
|
|
|
NSString* T1 = MetalCast::getScalarType(dstTensor->getType(), mtbn->useFp16InsteadFp32());
|
|
NSString* T0 = MetalCast::getScalarType(srcTensor->getType(), mtbn->useFp16InsteadFp32());
|
|
std::vector<std::string> keys = {
|
|
std::string([T0 UTF8String]),
|
|
std::string([T1 UTF8String]),
|
|
std::string([CUSTOM UTF8String]),
|
|
"binary_broadcast"
|
|
};
|
|
auto pipeline = mtbn->runtime()->findPipeline(keys);
|
|
if (nil == pipeline) {
|
|
MTLCompileOptions *compileOptions = [[MTLCompileOptions alloc] init];
|
|
compileOptions.preprocessorMacros = @{
|
|
@"T0" : T0,
|
|
@"T1" : T1,
|
|
@"CUSTOM" : CUSTOM,
|
|
};
|
|
pipeline = mtbn->makeComputePipelineWithSourceOption(gBinaryBroadcast, "loop_binary", compileOptions);
|
|
mtbn->runtime()->insertPipeline(keys, pipeline);
|
|
}
|
|
if (nil == pipeline) {
|
|
MNN_ERROR("Create Binary Broadcast pipeline error\n");
|
|
}
|
|
mPipeline = pipeline;
|
|
}
|
|
virtual ~MetalBinaryBroadCast() {
|
|
auto mtbn = static_cast<MetalBackend*>(backend());
|
|
mtbn->returnConstBuffer(mParam);
|
|
}
|
|
virtual ErrorCode onResize(const std::vector<Tensor *>& inputs, const std::vector<Tensor *>& outputs) override {
|
|
_setTensorStack(mTensors, inputs, outputs, mLoop);
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
auto size = cmd->size()->data();
|
|
auto srcStride0 = cmd->view()->GetAs<View>(1)->stride()->data();
|
|
auto srcStride1 = cmd->view()->GetAs<View>(2)->stride()->data();
|
|
auto dstStride = cmd->view()->GetAs<View>(0)->stride()->data();
|
|
mTotalSize = size[0] * size[1] * size[2];
|
|
auto param = reinterpret_cast<BinaryBroadCastInfo*>([mParam contents]);
|
|
for (int i=0; i<3; ++i) {
|
|
param->size[i] = size[i];
|
|
param->srcview0[i] = srcStride0[i];
|
|
param->srcview1[i] = srcStride1[i];
|
|
param->dstview[i] = dstStride[i];
|
|
}
|
|
param->srcview0[3] = cmd->view()->GetAs<View>(1)->offset();
|
|
param->srcview1[3] = cmd->view()->GetAs<View>(2)->offset();
|
|
param->dstview[3] = cmd->view()->GetAs<View>(0)->offset();
|
|
param->size[3] = size[0] * size[1] * size[2];
|
|
return NO_ERROR;
|
|
}
|
|
virtual void onEncode(const std::vector<Tensor *>& inputs, const std::vector<Tensor *>& outputs,
|
|
id<MTLComputeCommandEncoder> encoder) override {
|
|
|
|
auto cmd = mLoop->commands()->GetAs<RegionCommand>(mCmdIndex);
|
|
auto dstTensor = mTensors[cmd->indexes()->data()[0]];
|
|
auto srcTensor = mTensors[cmd->indexes()->data()[1]];
|
|
auto srcTensor1 = mTensors[cmd->indexes()->data()[2]];
|
|
[encoder setComputePipelineState:mPipeline];
|
|
MetalBackend::setTensor(dstTensor, encoder, 0);
|
|
MetalBackend::setTensor(srcTensor, encoder, 1);
|
|
MetalBackend::setTensor(srcTensor1, encoder, 2);
|
|
[encoder setBuffer:mParam offset:0 atIndex:3];
|
|
[encoder dispatchThreadgroups:MTLSizeMake(UP_DIV(mTotalSize, 256), 1, 1) threadsPerThreadgroup:MTLSizeMake(256, 1, 1)];
|
|
}
|
|
private:
|
|
const LoopParam* mLoop;
|
|
id<MTLComputePipelineState> mPipeline;
|
|
id<MTLBuffer> mParam;
|
|
std::vector<Tensor*> mTensors;
|
|
int mTotalSize;
|
|
int mCmdIndex = 0;
|
|
};
|
|
|
|
class MetalLoop : public MetalExecution {
|
|
public:
|
|
MetalLoop(const LoopParam* loop, Backend *bn, const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &outputs) : MetalExecution(bn) {
|
|
mLoop = loop;
|
|
auto mtbn = static_cast<MetalBackend *>(bn);
|
|
auto context = (__bridge MNNMetalContext *)mtbn->context();
|
|
mTensors.resize(mLoop->tensorNumber());
|
|
_setTensorStack(mTensors, inputs, outputs, mLoop);
|
|
|
|
// Init
|
|
if(mLoop->initCommand() != nullptr) {
|
|
mNeedInit = true;
|
|
std::string shader = "set_copy";
|
|
auto dstTensor = mTensors[mLoop->initCommand()->GetAs<RegionCommand>(0)->indexes()->data()[0]];
|
|
NSString* T = MetalCast::getScalarType(dstTensor->getType(), mtbn->useFp16InsteadFp32());
|
|
|
|
auto cmd = mLoop->initCommand()->GetAs<RegionCommand>(0);
|
|
if (cmd->op() == nullptr){
|
|
shader = "set_zero";
|
|
} else {
|
|
mInitParam = [context newDeviceBuffer:sizeof(InitInfo) access:CPUWriteOnly];
|
|
}
|
|
std::vector<std::string> keys = {
|
|
std::string([T UTF8String]),
|
|
"init_region",
|
|
shader
|
|
};
|
|
auto pipeline = mtbn->runtime()->findPipeline(keys);
|
|
if (nil == pipeline) {
|
|
MTLCompileOptions *compileOptions = [[MTLCompileOptions alloc] init];
|
|
compileOptions.preprocessorMacros = @{
|
|
@"T" : T,
|
|
};
|
|
pipeline = mtbn->makeComputePipelineWithSourceOption(gInitRegion, shader.c_str(), compileOptions);
|
|
mtbn->runtime()->insertPipeline(keys, pipeline);
|
|
}
|
|
if (nil == pipeline) {
|
|
MNN_ERROR("Create gather init pipeline error\n");
|
|
}
|
|
mInitPipeline = pipeline;
|
|
}
|
|
|
|
bool valid = true;
|
|
for (int i=0; i<loop->commands()->size(); ++i) {
|
|
auto cmd = loop->commands()->GetAs<RegionCommand>(i);
|
|
auto subop = cmd->op();
|
|
if (OpType_UnaryOp == subop->type() && nullptr == subop->main() && cmd->fuse() < 0) {
|
|
mExecutions.emplace_back(std::make_shared<MetalGather>(loop, bn, inputs, outputs, i));
|
|
} else if (OpType_MatMul == subop->type() && loop->parallel()) {
|
|
mExecutions.emplace_back(std::make_shared<MetalBatchMatMul>(loop, bn, i));
|
|
} else if (OpType_BinaryOp == subop->type() && cmd->fuse() < 0 && 1 == loop->loopNumber()) {
|
|
mExecutions.emplace_back(std::make_shared<MetalBinaryBroadCast>(loop, bn, mTensors, MetalBinary::convert(cmd->op()->main_as_BinaryOp()->opType(), mTensors[cmd->indexes()->data()[1]]->getType().code == halide_type_float), i));
|
|
} else {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!valid) {
|
|
mExecutions.clear();
|
|
}
|
|
}
|
|
virtual ~MetalLoop() = default;
|
|
|
|
virtual ErrorCode onResize(const std::vector<Tensor *>& inputs, const std::vector<Tensor *>& outputs) override {
|
|
// Init
|
|
if(mNeedInit) {
|
|
_setTensorStack(mTensors, inputs, outputs, mLoop);
|
|
auto initCmd = mLoop->initCommand()->GetAs<RegionCommand>(0);
|
|
auto data = reinterpret_cast<InitInfo*>([mInitParam contents]);
|
|
|
|
auto srcStride = initCmd->view()->GetAs<View>(1)->stride()->data();
|
|
auto dstStride = initCmd->view()->GetAs<View>(0)->stride()->data();
|
|
auto dataSize = initCmd->size()->data();
|
|
for (int i = 0; i < 3; ++i) {
|
|
data->srcStride[i] = srcStride[i];
|
|
data->dstStride[i] = dstStride[i];
|
|
data->size[i] = dataSize[i];
|
|
}
|
|
|
|
auto initDstTensor = mTensors[initCmd->indexes()->data()[0]];
|
|
auto initSrcTensor = mTensors[initCmd->indexes()->data()[1]];
|
|
auto initInputSize = initSrcTensor->usize() / initSrcTensor->buffer().type.bytes();
|
|
auto initOutputSize = initDstTensor->usize() / initDstTensor->buffer().type.bytes();
|
|
data->totalSize[0] = initInputSize;
|
|
data->totalSize[1] = initOutputSize;
|
|
|
|
auto backend = static_cast<MetalBackend *>(this->backend());
|
|
auto context = (__bridge MNNMetalContext *)backend->context();
|
|
mInitThreads = [context computeBestGroupAndLocal:mInitPipeline threads:MTLSizeMake(data->size[0], data->size[1], data->size[2])];
|
|
}
|
|
|
|
// Loop commands
|
|
for (auto& exe : mExecutions) {
|
|
auto code = exe->onResize(inputs, outputs);
|
|
if (NO_ERROR != code) {
|
|
return code;
|
|
}
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
virtual void onEncode(const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &outputs, id<MTLComputeCommandEncoder> encoder) override {
|
|
// Init
|
|
if(mNeedInit) {
|
|
auto initCmd = mLoop->initCommand()->GetAs<RegionCommand>(0);
|
|
[encoder setComputePipelineState:mInitPipeline];
|
|
auto dstTensor = mTensors[initCmd->indexes()->data()[0]];
|
|
auto srcTensor = mTensors[initCmd->indexes()->data()[1]];
|
|
MetalBackend::setTensor(dstTensor, encoder, 0);
|
|
MetalBackend::setTensor(srcTensor, encoder, 1);
|
|
[encoder setBuffer:mInitParam offset:0 atIndex:2];
|
|
[encoder dispatchThreadgroups:mInitThreads.first threadsPerThreadgroup:mInitThreads.second];
|
|
}
|
|
// Loop commands
|
|
for (auto& exe : mExecutions) {
|
|
exe->onEncode(inputs, outputs, encoder);
|
|
}
|
|
}
|
|
bool isValid() {
|
|
return !mExecutions.empty();
|
|
}
|
|
private:
|
|
const LoopParam* mLoop;
|
|
std::vector<std::shared_ptr<MetalExecution>> mExecutions;
|
|
std::vector<Tensor*> mTensors;
|
|
// For Init
|
|
bool mNeedInit = false;
|
|
std::pair<MTLSize, MTLSize> mInitThreads;
|
|
id<MTLComputePipelineState> mInitPipeline;
|
|
id<MTLBuffer> mInitParam;
|
|
|
|
};
|
|
|
|
class MetalLoopCreator : public MetalBackend::Creator {
|
|
public:
|
|
virtual Execution *onCreate(const std::vector<Tensor *> &inputs, const MNN::Op *op, Backend *bn, const std::vector<Tensor *> &outputs) const {
|
|
auto loop = op->main_as_LoopParam();
|
|
if (nullptr == loop || loop->commands() == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Make Tensor Stack
|
|
if (1 == loop->commands()->size()) {
|
|
auto cmd = loop->commands()->GetAs<RegionCommand>(0);
|
|
auto subop = cmd->op();
|
|
if (OpType_UnaryOp == subop->type() && nullptr == subop->main() && cmd->fuse() < 0 && nullptr == loop->initCommand()) {
|
|
return new MetalGather(loop, bn, inputs, outputs);
|
|
}
|
|
if (OpType_MatMul == subop->type() && loop->parallel() && nullptr == loop->initCommand()) {
|
|
return new MetalBatchMatMul(loop, bn);
|
|
}
|
|
if (OpType_BinaryOp == subop->type() && cmd->fuse() < 0 && 1 == loop->loopNumber() && nullptr == loop->initCommand()) {
|
|
std::vector<MNN::Tensor*> tensors(loop->tensorNumber());
|
|
_setTensorStack(tensors, inputs, outputs, loop);
|
|
auto srcTensor = tensors[cmd->indexes()->data()[1]];
|
|
|
|
NSString* CUSTOM = MetalBinary::convert(cmd->op()->main_as_BinaryOp()->opType(), srcTensor->getType().code == halide_type_float);
|
|
if (nil == CUSTOM) {
|
|
MNN_ERROR("Metal Don't support binary - %d \n", cmd->op()->main_as_BinaryOp()->opType());
|
|
return nullptr;
|
|
}
|
|
return new MetalBinaryBroadCast(loop, bn, tensors, CUSTOM);
|
|
}
|
|
}
|
|
// General Case
|
|
auto exe = new MetalLoop(loop, bn, inputs, outputs);
|
|
if (exe->isValid()) {
|
|
return exe;
|
|
}
|
|
delete exe;
|
|
return nullptr;
|
|
|
|
}
|
|
};
|
|
REGISTER_METAL_OP_CREATOR(MetalLoopCreator, OpType_While);
|
|
} // namespace MNN
|
|
#endif /* MNN_METAL_ENABLED */
|