Files
alibaba--mnn/source/backend/cpu/arm/CommonOptFunctionNeon.cpp
2026-07-13 13:33:03 +08:00

1973 lines
75 KiB
C++

#include "core/Macro.h"
#include "../compute/CommonOptFunction.h"
#ifdef MNN_USE_NEON
#include <arm_neon.h>
#include "./FunctionSummary.hpp"
#include "core/MemoryFormater.h"
extern "C" {
void MNNTranspose32Bit4x4(int32_t* dstO, const int32_t* srcO, int32_t* dim);
void MNNTranspose16Bit8x8(int16_t* dstO, const int16_t* srcO, int32_t* dim);
}
static inline float vmaxvq_f32_compat(float32x4_t v) {
#if defined(__aarch64__)
return vmaxvq_f32(v);
#else
float32x2_t p = vpmax_f32(vget_low_f32(v), vget_high_f32(v));
p = vpmax_f32(p, p);
return vget_lane_f32(p, 0);
#endif
}
static inline float vminvq_f32_compat(float32x4_t v) {
#if defined(__aarch64__)
return vminvq_f32(v);
#else
float32x2_t step1 = vpmin_f32(vget_low_f32(v), vget_high_f32(v));
step1 = vpmin_f32(step1, step1);
return vget_lane_f32(step1, 0);
#endif
}
static inline float vaddvq_f32_compat(float32x4_t v) {
#if defined(__aarch64__)
return vaddvq_f32(v);
#else
float32x2_t p = vpadd_f32(vget_low_f32(v), vget_high_f32(v));
p = vpadd_f32(p, p);
return vget_lane_f32(p, 0);
#endif
}
#ifdef MNN_SUPPORT_TRANSFORMER_FUSE
#ifdef __aarch64__
void MNNQuantAttentionKey(int8_t* dst, const float* source, float* sumKeyPtr, float* maxKeyPtr, int32_t* params) {
int32_t kvNumHead = params[0];
int32_t seqLen = params[1];
int32_t headDim = params[2];
int32_t blockNum = params[3];
int32_t eP = params[4];
int32_t lP = params[5];
int32_t hP = params[6];
int32_t pastLength = params[7];
int32_t kvHeadIdx = params[8];
auto blockHeadDim = UP_DIV(headDim, blockNum);
auto weightStride1 = ROUND_UP(blockHeadDim, lP) * hP;
auto weightStride2 = lP * hP;
auto packedWeightStride1 = weightStride1 + 2 * 4 * hP;
int8_t tempBuffer[8];
if (seqLen > 1) {
// get max
for (int s = 0; s < seqLen; ++s) {
const float* keySrc = source + s * kvNumHead * headDim + kvHeadIdx * headDim;
int d = 0;
for (; d <= headDim - 8; d += 8) {
float32x4_t max_vec0 = vld1q_f32(maxKeyPtr + d);
float32x4_t max_vec1 = vld1q_f32(maxKeyPtr + d + 4);
float32x4_t src_vec0 = vld1q_f32(keySrc + d);
float32x4_t src_vec1 = vld1q_f32(keySrc + d + 4);
max_vec0 = vmaxq_f32(max_vec0, src_vec0);
max_vec1 = vmaxq_f32(max_vec1, src_vec1);
vst1q_f32(maxKeyPtr + d, max_vec0);
vst1q_f32(maxKeyPtr + d + 4, max_vec1);
}
for (; d <= headDim - 4; d += 4) {
float32x4_t max_vec = vld1q_f32(maxKeyPtr + d);
float32x4_t src_vec = vld1q_f32(keySrc + d);
max_vec = vmaxq_f32(max_vec, src_vec);
vst1q_f32(maxKeyPtr + d, max_vec);
}
for (; d < headDim; ++d) {
maxKeyPtr[d] = ALIMAX(maxKeyPtr[d], keySrc[d]);
}
}
}
for (int s = 0; s < seqLen; s++) {
const float* keySrc = source + s * kvNumHead * headDim + kvHeadIdx * headDim;
float32x4_t min_vec = vdupq_n_f32(keySrc[0] - maxKeyPtr[0]);
float32x4_t max_vec = vdupq_n_f32(keySrc[0] - maxKeyPtr[0]);
int d = 0;
for (; d <= headDim - 4; d += 4) {
float32x4_t src_vec = vld1q_f32(keySrc + d);
float32x4_t max_key_vec = vld1q_f32(maxKeyPtr + d);
float32x4_t keydata_vec = vsubq_f32(src_vec, max_key_vec);
min_vec = vminq_f32(min_vec, keydata_vec);
max_vec = vmaxq_f32(max_vec, keydata_vec);
}
// Reduction
float minKey = vminvq_f32_compat(min_vec);
float maxKey = vmaxvq_f32_compat(max_vec);
// remain headDim
for (; d < headDim; ++d) {
auto keydata = keySrc[d] - maxKeyPtr[d];
minKey = ALIMIN(minKey, keydata);
maxKey = ALIMAX(maxKey, keydata);
}
int outIndex = (pastLength + s) / hP;
int inIndex = (pastLength + s) % hP;
float range = maxKey - minKey;
float quantScaleVal = 0;
float biasVal = minKey + 128.0f * (range) / 255.0f;
if (range <= 1e-6f) {
quantScaleVal = 0.0f;
} else {
quantScaleVal = 255.0f / range;
}
for (int k = 0; k < blockNum; ++k) {
int8_t* weightDstBase = dst + outIndex * blockNum * packedWeightStride1 + k * packedWeightStride1;
float* scaleDst = (float*)(weightDstBase + weightStride1);
float* biasDst = scaleDst + hP;
scaleDst[inIndex] = range / 255.f;
biasDst[inIndex] = biasVal;
float32x4_t scaleVec = vdupq_n_f32(quantScaleVal);
float32x4_t negBiasVec = vdupq_n_f32(-minKey);
float32x4_t neg128Vec = vdupq_n_f32(-128.0f);
const float* currentKeyBlock = keySrc + k * blockHeadDim;
const float* currentMaxBlock = maxKeyPtr + k * blockHeadDim;
int32x4_t sumInt32_0 = vdupq_n_s32(0);
int32x4_t sumInt32_1 = vdupq_n_s32(0);
int headDimIdx = 0;
for (; headDimIdx <= blockHeadDim - 8; headDimIdx += 8) {
float32x4_t srcVec0 = vld1q_f32(currentKeyBlock + headDimIdx);
float32x4_t srcVec1 = vld1q_f32(currentKeyBlock + headDimIdx + 4);
float32x4_t maxVec0 = vld1q_f32(currentMaxBlock + headDimIdx);
float32x4_t maxVec1 = vld1q_f32(currentMaxBlock + headDimIdx + 4);
float32x4_t keyData0 = vsubq_f32(srcVec0, maxVec0);
float32x4_t keyData1 = vsubq_f32(srcVec1, maxVec1);
keyData0 = vaddq_f32(keyData0, negBiasVec);
keyData1 = vaddq_f32(keyData1, negBiasVec);
keyData0 = vmulq_f32(keyData0, scaleVec);
keyData1 = vmulq_f32(keyData1, scaleVec);
keyData0 = vaddq_f32(keyData0, neg128Vec);
keyData1 = vaddq_f32(keyData1, neg128Vec);
int32x4_t s32_0 = vcvtaq_s32_f32(keyData0);
int32x4_t s32_1 = vcvtaq_s32_f32(keyData1);
sumInt32_0 = vaddq_s32(sumInt32_0, s32_0);
sumInt32_1 = vaddq_s32(sumInt32_1, s32_1);
int16x4_t s16_0 = vmovn_s32(s32_0);
int16x4_t s16_1 = vmovn_s32(s32_1);
int16x8_t s16Combined = vcombine_s16(s16_0, s16_1);
int8x8_t s8Vec = vqmovn_s16(s16Combined);
if (lP == 8) {
int i = headDimIdx / lP;
int8_t* dstPtr = weightDstBase + i * weightStride2 + inIndex * lP;
vst1_s8(dstPtr, s8Vec);
} else if (lP == 4) {
vst1_s8(tempBuffer, s8Vec);
int iLow = headDimIdx / lP;
int iHigh = (headDimIdx + 4) / lP;
int8_t* dstPtrLow = weightDstBase + iLow * weightStride2 + inIndex * lP;
int8_t* dstPtrHigh = weightDstBase + iHigh * weightStride2 + inIndex * lP;
std::memcpy(dstPtrLow, tempBuffer, 4);
std::memcpy(dstPtrHigh, tempBuffer + 4, 4);
} else {
vst1_s8(tempBuffer, s8Vec);
for (int k = 0; k < 8; ++k) {
int headDimCurr = headDimIdx + k;
int i = headDimCurr / lP;
int j = headDimCurr % lP;
weightDstBase[i * weightStride2 + inIndex * lP + j] = tempBuffer[k];
}
}
}
int32_t sumInt32 = vaddvq_s32(sumInt32_0) + vaddvq_s32(sumInt32_1);
// remain L
for (; headDimIdx < blockHeadDim; ++headDimIdx) {
int i = headDimIdx / lP;
int j = headDimIdx % lP;
float keyVal = currentKeyBlock[headDimIdx] - currentMaxBlock[headDimIdx];
float quant_val = (keyVal - minKey) * quantScaleVal - 128.0f;
int32_t rounded_val = static_cast<int32_t>(roundf(quant_val));
int8_t finalVal = static_cast<int8_t>(std::max(-128, std::min(127, rounded_val)));
weightDstBase[i * weightStride2 + inIndex * lP + j] = finalVal;
sumInt32 += finalVal;
}
// store sum
sumKeyPtr[outIndex * hP + inIndex] = (float)sumInt32 * range / 255.f + (minKey * blockHeadDim + 128.0f * range * blockHeadDim / 255.0f);
}
}
}
void MNNQuantAttentionValue(int8_t* dst, const float* source, float* valueSum, int32_t* params) {
// float value src : [kvSeq,kvNumHead,headDim]
// int8_t value dest: [updiv(maxLength,flashAttentionBlockKv), updiv(headDim,hp),updiv(flashAttentionBlockKv,lp),hp,lp]
// float value sum: [updiv(maxLength,flashAttentionBlockKv), roundup(headDim,hp)]
int32_t kvNumHead = params[0];
int32_t seqLen = params[1];
int32_t headDim = params[2];
int32_t blockNum = params[3];
int32_t maxLength = params[4];
int32_t lP = params[5];
int32_t hP = params[6];
int32_t pastLength = params[7];
int32_t kvHeadIdx = params[8];
int32_t flashAttentionBlockKv = params[9];
auto blockKvseq = UP_DIV(seqLen + pastLength, blockNum);
auto weightStride2 = lP * hP;
auto weightStride1 = UP_DIV(flashAttentionBlockKv, lP) * weightStride2;
auto packedStride1 = (int)(weightStride1 + 2 * hP * sizeof(float));
auto packedStride0 = UP_DIV(headDim, hP) * packedStride1;
auto srcStride0 = kvNumHead * headDim;
auto sourceFp32 = (float*)source;
// quant scale & bias
if (pastLength == 0) {
for (int d = 0; d < headDim; ++d) {
float* scalePtr = (float*)(dst + (d / hP) * packedStride1 + weightStride1) + (d % hP);
float* biasPtr = scalePtr + hP;
// find min,max
float dMax = sourceFp32[d + kvHeadIdx * headDim];
float dMin = dMax;
for (int s = 0; s < seqLen; ++s) {
float data = sourceFp32[s * srcStride0 + d + kvHeadIdx * headDim];
dMax = ALIMAX(dMax, data);
dMin = ALIMIN(dMin, data);
}
// scale & bias
float range = dMax - dMin;
if (range < 1e-6) {
scalePtr[0] = 0.f;
biasPtr[0] = dMax;
} else {
float scale = range / 255.f;
float bias = range / 255.f * 128.f + dMin;
scalePtr[0] = scale;
biasPtr[0] = bias;
}
}
}
// copy the scale&bias to each blockKv
// pastLength == 0: First time prefill
// pastLength % flashAttentionBlockKv == 0: Open a new blockKv
if (pastLength == 0 || (pastLength % flashAttentionBlockKv) == 0) {
int32_t d0 = UP_DIV(maxLength, flashAttentionBlockKv);
int32_t d1 = UP_DIV(headDim, hP);
for (int k = 0; k < d0; ++k) {
for (int r = 0; r < d1; ++r) {
float* scalePtr = (float*)(dst + k * packedStride0 + r * packedStride1 + weightStride1);
float* biasPtr = scalePtr + hP;
memcpy(scalePtr, dst + r * packedStride1 + weightStride1, hP * sizeof(float));
memcpy(biasPtr, dst + r * packedStride1 + weightStride1 + hP * sizeof(float), hP * sizeof(float));
}
}
}
// Quant fp16
for (int d = 0; d < headDim; ++d) {
// dst address
int idxBase = (d / hP) * packedStride1 + (d % hP) * lP;
int8_t* dstBase = dst + idxBase;
float* scaleBase = (float*)(dst + (d / hP) * packedStride1 + weightStride1) + (d % hP);
float* biasBase = scaleBase + hP;
float* sumBase = valueSum + (d / hP) * hP + (d % hP);
float qscale = scaleBase[0] < 1e-6 ? 0 : 1.0f / scaleBase[0];
float qbias = scaleBase[0] < 1e-6 ? 0 : (-biasBase[0] / scaleBase[0]);
// quant
for (int s = 0; s < seqLen; ++s) {
int kvSeqIndx = s + pastLength;
int idxInner = (kvSeqIndx / flashAttentionBlockKv) * packedStride0 + (kvSeqIndx % flashAttentionBlockKv) / lP * weightStride2 + (kvSeqIndx % flashAttentionBlockKv) % lP;
float xf = sourceFp32[s * srcStride0 + d + kvHeadIdx * headDim];
int8_t xq = ALIMAX(ALIMIN(127, static_cast<int32_t>(roundf(xf * qscale + qbias))), -128);
dstBase[idxInner] = xq;
// sum
int idxSum = (kvSeqIndx / flashAttentionBlockKv) * ROUND_UP(headDim, hP);
sumBase[idxSum] += ((float)xq * scaleBase[0] + biasBase[0]);
}
}
}
#endif // __aarch64__
#endif // MNN_SUPPORT_TRANSFORMER_FUSE
void MNNTranspose32Bit(int32_t* dstO, const int32_t* srcO, int32_t* dim) {
int w = dim[0];
int h = dim[1];
auto wC4 = w / 4;
auto hC4 = h / 4;
int srcStride = dim[2];
int dstStride = dim[3];
if (wC4 > 0 && hC4 > 0) {
MNNTranspose32Bit4x4(dstO, srcO, dim);
}
// Down
for (int i=hC4 * 4; i<h; ++i) {
auto si = srcO + i;
auto di = dstO + i * dstStride;
for (int j=0; j<w; ++j) {
auto sj = si + j * srcStride;
auto dj = di + j;
*dj = *sj;
}
}
// Right
for (int i=0; i<hC4 * 4; ++i) {
auto si = srcO + i;
auto di = dstO + i * dstStride;
for (int j=wC4 * 4; j<w; ++j) {
auto sj = si + j * srcStride;
auto dj = di + j;
*dj = *sj;
}
}
}
void MNNTranspose16Bit(int16_t* dstO, const int16_t* srcO, int32_t* dim) {
int w = dim[0];
int h = dim[1];
auto wC8 = w / 8;
auto hC8 = h / 8;
int srcStride = dim[2];
int dstStride = dim[3];
if (wC8 > 0 && hC8 > 0) {
MNNTranspose16Bit8x8(dstO, srcO, dim);
}
// Down
for (int i = hC8 * 8; i < h; ++i) {
auto si = srcO + i;
auto di = dstO + i * dstStride;
for (int j = 0; j < w; ++j) {
auto sj = si + j * srcStride;
auto dj = di + j;
*dj = *sj;
}
}
// Right
for (int i = 0; i < hC8 * 8; ++i) {
auto si = srcO + i;
auto di = dstO + i * dstStride;
for (int j = wC8 * 8; j < w; ++j) {
auto sj = si + j * srcStride;
auto dj = di + j;
*dj = *sj;
}
}
}
#define EXP_APPROX_MIN_INPUT vdupq_n_f32(-88.0f)
#define EXP_APPROX_MAX_INPUT vdupq_n_f32(88.0f)
#define EXP_APPROX_LN2 vdupq_n_f32(0.69314718056f) // ln(2)
#define EXP_APPROX_LN2_INV vdupq_n_f32(1.44269504089f) // 1/ln(2)
// Fourth-order polynomial approximation coefficients of exp(r):
// P(x) = c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
#define EXP_APPROX_C4 vdupq_n_f32(0.0416624f)
#define EXP_APPROX_C3 vdupq_n_f32(0.166665f)
#define EXP_APPROX_C2 vdupq_n_f32(0.500000f)
#define EXP_APPROX_C1 vdupq_n_f32(1.0f)
#define EXP_APPROX_C0 vdupq_n_f32(1.0f)
#ifndef __aarch64__
static inline float32x4_t vrndaq_f32_compat(float32x4_t x) {
float32x4_t sign = vbslq_f32(vdupq_n_u32(0x80000000), x, vdupq_n_f32(0.0f));
return vcvtq_f32_s32(vcvtq_s32_f32(vaddq_f32(x, vbslq_f32(vcltq_f32(x, vdupq_n_f32(0.0f)), vdupq_n_f32(-0.5f), vdupq_n_f32(0.5f)))));
}
#endif
static inline float32x4_t expApprox(float32x4_t x) {
x = vminq_f32(vmaxq_f32(x, EXP_APPROX_MIN_INPUT), EXP_APPROX_MAX_INPUT);
float32x4_t k_float;
float32x4_t r;
float32x4_t exp_r;
#if defined(__aarch64__)
k_float = vrndaq_f32(vmulq_f32(x, EXP_APPROX_LN2_INV));
// r = x - k * ln(2)
r = vfmsq_f32(x, k_float, EXP_APPROX_LN2);
// P(r) = (c0 + c2*r^2 + c4*r^4) + r*(c1 + c3*r^2)
float32x4_t r2 = vmulq_f32(r, r);
float32x4_t p_odd = vfmaq_f32(EXP_APPROX_C1, EXP_APPROX_C3, r2);
float32x4_t p_even = vfmaq_f32(EXP_APPROX_C0, EXP_APPROX_C2, r2);
p_even = vfmaq_f32(p_even, EXP_APPROX_C4, vmulq_f32(r2, r2));
exp_r = vfmaq_f32(p_even, p_odd, r);
#else
k_float = vrndaq_f32_compat(vmulq_f32(x, EXP_APPROX_LN2_INV));
r = vsubq_f32(x, vmulq_f32(k_float, EXP_APPROX_LN2));
// 2. c0 + r*(c1 + r*(c2 + r*(c3 + r*c4)))
exp_r = vmlaq_f32(EXP_APPROX_C3, EXP_APPROX_C4, r); // c3 + c4*r
exp_r = vmlaq_f32(EXP_APPROX_C2, exp_r, r); // c2 + r*(...)
exp_r = vmlaq_f32(EXP_APPROX_C1, exp_r, r); // c1 + r*(...)
exp_r = vmlaq_f32(EXP_APPROX_C0, exp_r, r); // c0 + r*(...)
#endif
int32x4_t k_int = vcvtq_s32_f32(k_float);
int32x4_t k_shifted = vshlq_n_s32(k_int, 23);
return vreinterpretq_f32_s32(vaddq_s32(vreinterpretq_s32_f32(exp_r), k_shifted));
}
void MNNExpC8(float* dst, const float* src, float* offset, const float* parameters, size_t countC8) {
float32x4_t maxVec = vdupq_n_f32(offset[2]);
float32x4_t sumVec0 = vdupq_n_f32(0);
float32x4_t sumVec1 = vdupq_n_f32(0);
float32x4_t c0 = vdupq_n_f32(offset[0]);
float32x4_t c1 = vdupq_n_f32(offset[1]);
for (int i = 0; i < countC8; ++i) {
float32x4_t srcVec0 = vld1q_f32(src);
float32x4_t srcVec1 = vld1q_f32(src + 4);
auto subVec0 = vaddq_f32(vmulq_f32(srcVec0, c0), maxVec);
auto subVec1 = vaddq_f32(vmulq_f32(srcVec1, c0), maxVec);
auto expVec0 = vaddq_f32(expApprox(subVec0), c1);
auto expVec1 = vaddq_f32(expApprox(subVec1), c1);
vst1q_f32(dst, expVec0);
vst1q_f32(dst + 4, expVec1);
sumVec0 = vaddq_f32(sumVec0, expVec0);
sumVec1 = vaddq_f32(sumVec1, expVec1);
src += 8;
dst += 8;
}
sumVec0 = vaddq_f32(sumVec0, sumVec1);
float32x2_t sumP = vpadd_f32(vget_low_f32(sumVec0), vget_high_f32(sumVec0));
sumP = vpadd_f32(sumP, sumP);
offset[3] += vget_lane_f32(sumP, 0);
}
void MNNExp(float* destPtr, const float* srcPtr, float* offset, size_t size) {
float32x4_t maxVec = vdupq_n_f32(-offset[2]);
float32x4_t sumVec0 = vdupq_n_f32(0);
float32x4_t sumVec1 = vdupq_n_f32(0);
if (offset[0] == 1.f && offset[1] == 0.f) {
while (size >= 8) {
float32x4_t srcVec0 = vld1q_f32(srcPtr);
float32x4_t srcVec1 = vld1q_f32(srcPtr + 4);
auto subVec0 = vsubq_f32(srcVec0, maxVec);
auto subVec1 = vsubq_f32(srcVec1, maxVec);
auto expVec0 = expApprox(subVec0);
auto expVec1 = expApprox(subVec1);
vst1q_f32(destPtr, expVec0);
vst1q_f32(destPtr + 4, expVec1);
sumVec0 = vaddq_f32(sumVec0, expVec0);
sumVec1 = vaddq_f32(sumVec1, expVec1);
srcPtr += 8;
destPtr += 8;
size -= 8;
}
while (size >= 4) {
float32x4_t srcVec0 = vld1q_f32(srcPtr);
auto subVec0 = vsubq_f32(srcVec0, maxVec);
auto expVec0 = expApprox(subVec0);
sumVec0 = vaddq_f32(sumVec0, expVec0);
vst1q_f32(destPtr, expVec0);
srcPtr += 4;
destPtr += 4;
size -= 4;
}
//merge
sumVec0 = vaddq_f32(sumVec0, sumVec1);
float32x2_t sumP = vpadd_f32(vget_low_f32(sumVec0), vget_high_f32(sumVec0));
sumP = vpadd_f32(sumP, sumP);
auto newSum = vget_lane_f32(sumP, 0);
if (size > 0) {
float tmp[4];
memcpy(tmp, srcPtr, size * sizeof(float));
float32x4_t srcVec0 = vld1q_f32(tmp);
auto subVec0 = vsubq_f32(srcVec0, maxVec);
auto expVec0 = expApprox(subVec0);
vst1q_f32(tmp, expVec0);
for (int i = 0; i < size; ++i) {
newSum += tmp[i];
destPtr[i] = tmp[i];
}
}
offset[3] += newSum;
} else {
float32x4_t c0 = vdupq_n_f32(offset[0]);
float32x4_t c1 = vdupq_n_f32(offset[1]);
while (size >= 8) {
float32x4_t srcVec0 = vld1q_f32(srcPtr);
float32x4_t srcVec1 = vld1q_f32(srcPtr + 4);
auto subVec0 = vsubq_f32(vmulq_f32(srcVec0, c0), maxVec);
auto subVec1 = vsubq_f32(vmulq_f32(srcVec1, c0), maxVec);
auto expVec0 = vaddq_f32(expApprox(subVec0), c1);
auto expVec1 = vaddq_f32(expApprox(subVec1), c1);
vst1q_f32(destPtr, expVec0);
vst1q_f32(destPtr + 4, expVec1);
sumVec0 = vaddq_f32(sumVec0, expVec0);
sumVec1 = vaddq_f32(sumVec1, expVec1);
srcPtr += 8;
destPtr += 8;
size -= 8;
}
while (size >= 4) {
float32x4_t srcVec0 = vld1q_f32(srcPtr);
auto subVec0 = vsubq_f32(vmulq_f32(srcVec0, c0), maxVec);
auto expVec0 = vaddq_f32(expApprox(subVec0), c1);
sumVec0 = vaddq_f32(sumVec0, expVec0);
vst1q_f32(destPtr, expVec0);
srcPtr += 4;
destPtr += 4;
size -= 4;
}
//merge
sumVec0 = vaddq_f32(sumVec0, sumVec1);
float32x2_t sumP = vpadd_f32(vget_low_f32(sumVec0), vget_high_f32(sumVec0));
sumP = vpadd_f32(sumP, sumP);
auto newSum = vget_lane_f32(sumP, 0);
if (size > 0) {
float tmp[4];
memcpy(tmp, srcPtr, size * sizeof(float));
float32x4_t srcVec0 = vld1q_f32(tmp);
auto subVec0 = vsubq_f32(vmulq_f32(srcVec0, c0), maxVec);
auto expVec0 = vaddq_f32(expApprox(subVec0), c1);
vst1q_f32(tmp, expVec0);
for (int i = 0; i < size; ++i) {
newSum += tmp[i];
destPtr[i] = tmp[i];
}
}
offset[3] += newSum;
}
}
static inline void transposeAndStore4x4(const float* srcRowPtrs[4], float* dstColBase, size_t dstColStride) {
float32x4_t row0 = vld1q_f32(srcRowPtrs[0]);
float32x4_t row1 = vld1q_f32(srcRowPtrs[1]);
float32x4_t row2 = vld1q_f32(srcRowPtrs[2]);
float32x4_t row3 = vld1q_f32(srcRowPtrs[3]);
// Step 1: Transpose 2x2 blocks of 2-element vectors
float32x4x2_t t01 = vtrnq_f32(row0, row1);
float32x4x2_t t23 = vtrnq_f32(row2, row3);
// Step 2: Combine the results to get the full transpose
float32x4_t col0 = vcombine_f32(vget_low_f32(t01.val[0]), vget_low_f32(t23.val[0]));
float32x4_t col1 = vcombine_f32(vget_low_f32(t01.val[1]), vget_low_f32(t23.val[1]));
float32x4_t col2 = vcombine_f32(vget_high_f32(t01.val[0]), vget_high_f32(t23.val[0]));
float32x4_t col3 = vcombine_f32(vget_high_f32(t01.val[1]), vget_high_f32(t23.val[1]));
vst1q_f32(dstColBase, col0);
vst1q_f32(dstColBase + dstColStride, col1);
vst1q_f32(dstColBase + 2 * dstColStride, col2);
vst1q_f32(dstColBase + 3 * dstColStride, col3);
}
inline void smartCopy(void* dest, const void* src, size_t size) {
switch (size) {
case 1:
*(uint8_t*)dest = *(const uint8_t*)src;
break;
case 2:
*(uint16_t*)dest = *(const uint16_t*)src;
break;
case 4:
*(uint32_t*)dest = *(const uint32_t*)src;
break;
case 8:
*(uint64_t*)dest = *(const uint64_t*)src;
break;
default:
::memcpy(dest, src, size);
break;
}
}
void MNNPackForMatMul_A(float* dst, const float* src, size_t E, size_t L, size_t eP, size_t lP, size_t bytes) {
if (E == 0 || L == 0) {
return;
}
// source [E, L] <=> [E/eP, eP, L/lP, lP]
// dest [E/eP, L/lP, eP, lP]
if (lP > 1) {
auto eU = UP_DIV(E, eP);
auto lU = UP_DIV(L, lP);
const size_t lC = L / lP;
const size_t lR = L % lP;
const size_t copySizeBytes = (size_t)lP * bytes;
const size_t srcStride0 = (size_t)L * bytes;
const size_t dstStride0 = (size_t)lU * eP * lP * bytes;
const size_t dstStride1 = eP * lP * bytes;
const size_t dstStride2 = lP * bytes;
for (int i = 0; i < eU; ++i) {
const size_t xC = ALIMIN(eP, E - i * eP);
const uint8_t* APtr = (uint8_t*)src + (i * eP) * srcStride0;
uint8_t* ADst = (uint8_t*)dst + i * dstStride0;
if (lC > 0) {
for (int x = 0; x < xC; ++x) {
auto srcBase = APtr + x * srcStride0;
auto destBase = ADst + x * dstStride2;
for (int yy = 0; yy < lC; ++yy) {
auto srcPtr = srcBase + (size_t)yy * copySizeBytes;
auto destPtr = destBase + (size_t)yy * dstStride1;
smartCopy(destPtr, srcPtr, copySizeBytes);
}
}
}
if (lR > 0) {
const size_t remainderCopyBytes = (size_t)lR * bytes;
for (int x = 0; x < xC; ++x) {
auto srcPtr = APtr + x * srcStride0 + lC * lP * bytes;
auto destPtr = ADst + lC * dstStride1 + x * dstStride2;
::memcpy(destPtr, srcPtr, remainderCopyBytes);
::memset(destPtr + remainderCopyBytes, 0, copySizeBytes - remainderCopyBytes);
}
}
}
return;
}
const int kTileS = 4; // Tiling size for E dimension
const int kTileK = 4; // Tiling size for L dimension
const size_t dstSOuterStride = L * eP;
int s = 0;
for (; s + kTileS <= E; s += kTileS) {
const int sOuter = s / eP;
const int sInner = s % eP;
if (sInner + kTileS > eP) {
break;
}
float* dstSBase = dst + sOuter * dstSOuterStride + sInner;
const float* srcRowPtrs[kTileS];
srcRowPtrs[0] = src + (s + 0) * L;
srcRowPtrs[1] = src + (s + 1) * L;
srcRowPtrs[2] = src + (s + 2) * L;
srcRowPtrs[3] = src + (s + 3) * L;
int k = 0;
for (; k + kTileK <= L; k += kTileK) {
const float* currentSrcPtrs[kTileS];
currentSrcPtrs[0] = srcRowPtrs[0] + k;
currentSrcPtrs[1] = srcRowPtrs[1] + k;
currentSrcPtrs[2] = srcRowPtrs[2] + k;
currentSrcPtrs[3] = srcRowPtrs[3] + k;
float* dstKBase = dstSBase + k * eP;
transposeAndStore4x4(currentSrcPtrs, dstKBase, eP);
}
for (; k < L; ++k) {
float buffer[kTileS] = {
srcRowPtrs[0][k],
srcRowPtrs[1][k],
srcRowPtrs[2][k],
srcRowPtrs[3][k]
};
vst1q_f32(dstSBase + k * eP, vld1q_f32(buffer));
}
}
for (; s < E; ++s) {
const int sOuter = s / eP;
const int sInner = s % eP;
const float* srcRow = src + s * L;
float* dstSBase = dst + sOuter * dstSOuterStride + sInner;
for (int k = 0; k < L; ++k) {
dstSBase[k * eP] = srcRow[k];
}
}
}
void MNNSoftmaxFp32_Pack1(float* softmaxDst, const float* softmaxSrc, float* runningMax, float* runningSum, float* updateScale, int outside, int reduceSize, int kvSeqOffset, int validOffset, bool mask) {
for (int k = 0; k < outside; ++k) {
int currentValidSize = reduceSize;
bool isRowValid = true;
if (mask) {
if (kvSeqOffset > k + validOffset) {
isRowValid = false;
currentValidSize = 0;
if (updateScale) updateScale[k] = 1.0f;
} else {
currentValidSize = ALIMIN(reduceSize, k + (validOffset + 1) - kvSeqOffset);
}
}
float* dstRow = softmaxDst + k * reduceSize;
if (!isRowValid || currentValidSize == 0) {
memset(dstRow, 0, reduceSize * sizeof(float));
continue;
}
const float* srcRow = softmaxSrc + k * reduceSize;
// 1. Find max
float oldMax = std::numeric_limits<float>::lowest();
if (runningMax) oldMax = runningMax[k];
float32x4_t maxVec = vdupq_n_f32(std::numeric_limits<float>::lowest());
int i = 0;
// Unroll 4 vectors (16 floats) per iteration
for (; i <= currentValidSize - 16; i += 16) {
float32x4_t v0 = vld1q_f32(srcRow + i + 0);
float32x4_t v1 = vld1q_f32(srcRow + i + 4);
float32x4_t v2 = vld1q_f32(srcRow + i + 8);
float32x4_t v3 = vld1q_f32(srcRow + i + 12);
maxVec = vmaxq_f32(maxVec, v0);
maxVec = vmaxq_f32(maxVec, v1);
maxVec = vmaxq_f32(maxVec, v2);
maxVec = vmaxq_f32(maxVec, v3);
}
// Handle remaining blocks of 4
for (; i <= currentValidSize - 4; i += 4) {
maxVec = vmaxq_f32(maxVec, vld1q_f32(srcRow + i));
}
// Reduction
float newMax = vmaxvq_f32_compat(maxVec);
// Scalar Tail
for (; i < currentValidSize; ++i) {
newMax = ALIMAX(newMax, srcRow[i]);
}
float finalMax = ALIMAX(oldMax, newMax);
float32x4_t finalMaxVec = vdupq_n_f32(finalMax);
// 2. Exp & Sum & Store (4-way Unroll)
float sum = 0.0f;
float32x4_t sumVec = vdupq_n_f32(0.0f);
i = 0;
// Unroll 4 vectors (16 floats)
for (; i <= currentValidSize - 16; i += 16) {
float32x4_t v0 = vld1q_f32(srcRow + i + 0);
float32x4_t v1 = vld1q_f32(srcRow + i + 4);
float32x4_t v2 = vld1q_f32(srcRow + i + 8);
float32x4_t v3 = vld1q_f32(srcRow + i + 12);
// Sub Max
v0 = vsubq_f32(v0, finalMaxVec);
v1 = vsubq_f32(v1, finalMaxVec);
v2 = vsubq_f32(v2, finalMaxVec);
v3 = vsubq_f32(v3, finalMaxVec);
// Exp (Expensive operation, pipeline parallelism helps here)
v0 = expApprox(v0);
v1 = expApprox(v1);
v2 = expApprox(v2);
v3 = expApprox(v3);
// Accumulate Sum
sumVec = vaddq_f32(sumVec, v0);
sumVec = vaddq_f32(sumVec, v1);
sumVec = vaddq_f32(sumVec, v2);
sumVec = vaddq_f32(sumVec, v3);
// Store (Temporary exp values)
vst1q_f32(dstRow + i + 0, v0);
vst1q_f32(dstRow + i + 4, v1);
vst1q_f32(dstRow + i + 8, v2);
vst1q_f32(dstRow + i + 12, v3);
}
// Remaining blocks of 4
for (; i <= currentValidSize - 4; i += 4) {
float32x4_t v = vld1q_f32(srcRow + i);
v = vsubq_f32(v, finalMaxVec);
v = expApprox(v);
sumVec = vaddq_f32(sumVec, v);
vst1q_f32(dstRow + i, v);
}
// Scalar Tail
for (; i < currentValidSize; ++i) {
float val = expf(srcRow[i] - finalMax);
sum += val;
dstRow[i] = val;
}
sum += vaddvq_f32_compat(sumVec);
if (currentValidSize < reduceSize) {
memset(dstRow + currentValidSize, 0, (reduceSize - currentValidSize) * sizeof(float));
}
// 3. Update running max & sum or Normalize
if (runningMax && runningSum && updateScale) {
float scaleForSum = expf(oldMax - finalMax);
runningSum[k] = runningSum[k] * scaleForSum + sum;
runningMax[k] = finalMax;
updateScale[k] = scaleForSum;
} else {
if (runningMax && runningSum) {
sum += runningSum[k] * expf(oldMax - finalMax);
}
float scale = 1.0f / (sum + 1e-20f);
float32x4_t scaleVec = vdupq_n_f32(scale);
i = 0;
// Unroll 4 vectors
for (; i <= currentValidSize - 16; i += 16) {
float32x4_t v0 = vld1q_f32(dstRow + i + 0);
float32x4_t v1 = vld1q_f32(dstRow + i + 4);
float32x4_t v2 = vld1q_f32(dstRow + i + 8);
float32x4_t v3 = vld1q_f32(dstRow + i + 12);
vst1q_f32(dstRow + i + 0, vmulq_f32(v0, scaleVec));
vst1q_f32(dstRow + i + 4, vmulq_f32(v1, scaleVec));
vst1q_f32(dstRow + i + 8, vmulq_f32(v2, scaleVec));
vst1q_f32(dstRow + i + 12, vmulq_f32(v3, scaleVec));
}
for (; i <= currentValidSize - 4; i += 4) {
float32x4_t v = vld1q_f32(dstRow + i);
vst1q_f32(dstRow + i, vmulq_f32(v, scaleVec));
}
for (; i < currentValidSize; ++i) {
dstRow[i] *= scale;
}
}
}
}
void MNNSoftmaxFp32_Pack4(float* softmaxDst, const float* softmaxSrc, float* runningMax, float* runningSum, float* updateScale, int outside, int reduceSize, int kvSeqOffset, int validOffset, bool mask) {
const int packUnit = 4;
int reduceSizeOuter = UP_DIV(reduceSize, packUnit);
int stride0 = outside * packUnit;
for (int k = 0; k < outside; k += 4) {
int count = ALIMIN(4, outside - k);
int validTotalLen[4];
int fullBlocks[4];
int remain[4];
bool isRowValid[4];
for (int i = 0; i < count; ++i) {
int currentK = k + i;
if (mask && kvSeqOffset > currentK + validOffset) {
isRowValid[i] = false;
validTotalLen[i] = 0;
if (updateScale) updateScale[currentK] = 1.0f;
} else {
isRowValid[i] = true;
validTotalLen[i] = mask ? ALIMIN(reduceSize, currentK + (validOffset + 1) - kvSeqOffset) : reduceSize;
}
fullBlocks[i] = validTotalLen[i] / packUnit;
remain[i] = validTotalLen[i] % packUnit;
}
float currentMax[4];
float32x4_t vecMaxAccum[4];
float minVal = std::numeric_limits<float>::lowest();
float32x4_t minVec = vdupq_n_f32(minVal);
for (int i = 0; i < count; ++i) {
currentMax[i] = runningMax ? runningMax[k + i] : minVal;
vecMaxAccum[i] = minVec;
}
for (int j = 0; j < reduceSizeOuter; ++j) {
auto blockSrcBase = softmaxSrc + j * stride0 + k * packUnit;
for (int i = 0; i < count; ++i) {
if (!isRowValid[i]) continue;
if (j < fullBlocks[i]) {
float32x4_t val = vld1q_f32(blockSrcBase + i * packUnit);
vecMaxAccum[i] = vmaxq_f32(vecMaxAccum[i], val);
} else if (j == fullBlocks[i] && remain[i] > 0) {
auto ptr = blockSrcBase + i * packUnit;
for (int p = 0; p < remain[i]; ++p) {
currentMax[i] = ALIMAX(currentMax[i], ptr[p]);
}
}
}
}
// Finalize Max
float32x4_t finalMaxVec[4];
for (int i = 0; i < count; ++i) {
if (!isRowValid[i]) {
finalMaxVec[i] = vdupq_n_f32(0.0f);
continue;
}
float maxInVec = vmaxvq_f32_compat(vecMaxAccum[i]);
currentMax[i] = ALIMAX(currentMax[i], maxInVec);
finalMaxVec[i] = vdupq_n_f32(currentMax[i]);
}
float currentSum[4] = {0.0f};
float32x4_t vecSumAccum[4];
for (int i = 0; i < count; ++i) vecSumAccum[i] = vdupq_n_f32(0.0f);
for (int j = 0; j < reduceSizeOuter; ++j) {
auto blockSrcBase = softmaxSrc + j * stride0 + k * packUnit;
auto blockDstBase = softmaxDst + j * stride0 + k * packUnit;
for (int i = 0; i < count; ++i) {
if (!isRowValid[i]) {
memset(blockDstBase + i * packUnit, 0, sizeof(float) * 4);
continue;
}
auto dstPtr = blockDstBase + i * packUnit;
if (j < fullBlocks[i]) {
auto srcPtr = blockSrcBase + i * packUnit;
float32x4_t val = vld1q_f32(srcPtr);
val = vsubq_f32(val, finalMaxVec[i]);
val = expApprox(val);
vecSumAccum[i] = vaddq_f32(vecSumAccum[i], val);
vst1q_f32(dstPtr, val);
} else if (j == fullBlocks[i] && remain[i] > 0) {
auto srcPtr = blockSrcBase + i * packUnit;
for (int p = 0; p < remain[i]; ++p) {
float val = expf(srcPtr[p] - currentMax[i]);
currentSum[i] += val;
dstPtr[p] = val;
}
memset(dstPtr + remain[i], 0, (packUnit - remain[i]) * sizeof(float));
} else {
memset(dstPtr, 0, sizeof(float) * 4);
}
}
}
for (int i = 0; i < count; ++i) {
currentSum[i] += vaddvq_f32_compat(vecSumAccum[i]);
}
for (int i = 0; i < count; ++i) {
int currentK = k + i;
if (!isRowValid[i]) continue;
float scale;
if (runningMax && runningSum && updateScale) {
float oldMax = runningMax[currentK];
float scaleForSum = expf(oldMax - currentMax[i]);
runningSum[currentK] = runningSum[currentK] * scaleForSum + currentSum[i];
runningMax[currentK] = currentMax[i];
updateScale[currentK] = scaleForSum;
continue;
} else {
if (runningMax && runningSum) {
currentSum[i] += runningSum[currentK] * expf(runningMax[currentK] - currentMax[i]);
}
scale = 1.0f / (currentSum[i] + 1e-20f);
}
float32x4_t scaleVec = vdupq_n_f32(scale);
// Normalize Pass
for (int j = 0; j < reduceSizeOuter; ++j) {
if (j > fullBlocks[i] || (j == fullBlocks[i] && remain[i] == 0)) {
continue;
}
auto dstPtr = softmaxDst + j * stride0 + k * packUnit + i * packUnit;
if (j < fullBlocks[i]) {
float32x4_t val = vld1q_f32(dstPtr);
val = vmulq_f32(val, scaleVec);
vst1q_f32(dstPtr, val);
} else {
// Tail
for (int p = 0; p < remain[i]; ++p) {
dstPtr[p] *= scale;
}
}
}
}
}
}
void MNNSoftmax(float* softmaxDst, const float* softmaxSrc, float* runningMax, float* runningSum, float* updateScale, int outside, int reduceSize, int kvSeqOffset, int validOffset, int pack, bool mask) {
// source shape: [reduceSizeOuter, outside, reduceSizeInner]
// for C4, [up_div(reduceSize,4), outside,4] => reduceSizeOuter=up_div(reduceSize,4), reduceSizeInner=4
// for C, [outside, reduceSize] => reduceSizeOuter=1, reduceSizeInner=reduceSize
if (pack == 4) {
MNNSoftmaxFp32_Pack4(softmaxDst, softmaxSrc, runningMax, runningSum, updateScale, outside, reduceSize, kvSeqOffset, validOffset, mask);
return;
}
if (pack == 1) {
MNNSoftmaxFp32_Pack1(softmaxDst, softmaxSrc, runningMax, runningSum, updateScale, outside, reduceSize, kvSeqOffset, validOffset, mask);
return;
}
MNN_ERROR("MNNSoftmax not support pack != 1 and pack != 4\n");
return;
}
#ifndef MNN_USE_NEON
void MNNPackedSparseMatMulEpx1(float* C, const float* A, const float* B, size_t eSize, const size_t* parameter, const float* postParameters, const float* bias, unsigned int* NNZMap, int* dataOffsetMap) {
auto eP = parameter[0] / sizeof(float);
MNN_ASSERT((eP & 0x03) == 0); // In sparse calculate, eP should be evenly divided by 4
auto h = parameter[2];
auto l = parameter[1];
auto cStride = parameter[3] / sizeof(float);
auto hRemain = parameter[4];
auto bExtraStride = parameter[5] / sizeof(float);
auto bStride = bExtraStride + l * 4;
auto aStride = eP * l; // sizeof(float);
auto hC4 = UP_DIV(h, 4);
float minValue = -std::numeric_limits<float>().max();
float maxValue = std::numeric_limits<float>().max();
if (nullptr != postParameters) {
minValue = postParameters[2];
maxValue = postParameters[3];
}
const float32x4_t vmin = vld1q_dup_f32(&minValue);
const float32x4_t vmax = vld1q_dup_f32(&maxValue);
// MNN_PRINT("NEON MNNPackedSparseMatMul eP:%lu, eSize:%lu, l:%lu, h:%lu, cStride:%lu, aStride:%lu\n", eP, eSize, l, h, cStride, aStride);
const float* a = A;
size_t ie = 0;
for (ie = 0; ie + eP <= eSize; ie += eP) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
for (auto ih = 0; ih < h; ih++) {
auto ihPack = ih >> 2;
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihPack * cStride + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x4_t vacc0123 = vld1q_dup_f32(&initValue);
float32x4_t vacc4567 = vacc0123;
float32x4_t vacc89AB = vacc0123;
float32x4_t vaccCDEF = vacc0123;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
const float32x4_t va4567 = vld1q_f32(a + 4);
const float32x4_t va89AB = vld1q_f32(a + 8);
const float32x4_t vaCDEF = vld1q_f32(a + 12);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_dup_f32(w);
// MNN_PRINT("16-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc0123 = vfmaq_f32(vacc0123, va0123, w4);
vacc4567 = vfmaq_f32(vacc4567, va4567, w4);
vacc89AB = vfmaq_f32(vacc89AB, va89AB, w4);
vaccCDEF = vfmaq_f32(vaccCDEF, vaCDEF, w4);
}
vacc0123 = vminq_f32(vacc0123, vmax);
vacc4567 = vminq_f32(vacc4567, vmax);
vacc89AB = vminq_f32(vacc89AB, vmax);
vaccCDEF = vminq_f32(vaccCDEF, vmax);
vacc0123 = vmaxq_f32(vacc0123, vmin);
vacc4567 = vmaxq_f32(vacc4567, vmin);
vacc89AB = vmaxq_f32(vacc89AB, vmin);
vaccCDEF = vmaxq_f32(vaccCDEF, vmin);
// how to store faster: st4 / transpose /
vst1q_lane_f32(c, vacc0123, 0);
vst1q_lane_f32(c + 4, vacc0123, 1);
vst1q_lane_f32(c + 4 * 2, vacc0123, 2);
vst1q_lane_f32(c + 4 * 3, vacc0123, 3);
vst1q_lane_f32(c+ 4 * 4, vacc4567, 0);
vst1q_lane_f32(c + 4 * 5, vacc4567, 1);
vst1q_lane_f32(c + 4 * 6, vacc4567, 2);
vst1q_lane_f32(c + 4 * 7, vacc4567, 3);
vst1q_lane_f32(c + 4 * 8, vacc89AB, 0);
vst1q_lane_f32(c + 4 * 9, vacc89AB, 1);
vst1q_lane_f32(c + 4 * 10, vacc89AB, 2);
vst1q_lane_f32(c + 4 * 11, vacc89AB, 3);
vst1q_lane_f32(c + 4 * 12, vaccCDEF, 0);
vst1q_lane_f32(c + 4 * 13, vaccCDEF, 1);
vst1q_lane_f32(c + 4 * 14, vaccCDEF, 2);
vst1q_lane_f32(c + 4 * 15, vaccCDEF, 3);
}
a += aStride;
}
// const float* blockA = A + ie * l;
if (eSize & 0x08) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
for (auto ih = 0; ih < h; ih++) {
auto ihPack = ih >> 2;
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihPack * cStride + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x4_t vacc0123 = vld1q_dup_f32(&initValue);
float32x4_t vacc4567 = vacc0123;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
const float32x4_t va4567 = vld1q_f32(a + 4);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_dup_f32(w);
// MNN_PRINT("8-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-7]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {8});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc0123 = vfmaq_f32(vacc0123, va0123, w4);
vacc4567 = vfmaq_f32(vacc4567, va4567, w4);
}
vacc0123 = vminq_f32(vacc0123, vmax);
vacc4567 = vminq_f32(vacc4567, vmax);
vacc0123 = vmaxq_f32(vacc0123, vmin);
vacc4567 = vmaxq_f32(vacc4567, vmin);
// how to store faster: st4 / transpose /
vst1q_lane_f32(c, vacc0123, 0);
vst1q_lane_f32(c + 4, vacc0123, 1);
vst1q_lane_f32(c + 4 * 2, vacc0123, 2);
vst1q_lane_f32(c + 4 * 3, vacc0123, 3);
vst1q_lane_f32(c + 4 * 4, vacc4567, 0);
vst1q_lane_f32(c + 4 * 5, vacc4567, 1);
vst1q_lane_f32(c + 4 * 6, vacc4567, 2);
vst1q_lane_f32(c + 4 * 7, vacc4567, 3);
}
ie += 8;
a += 8;
}
if (eSize & 0x04) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// const float* a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
for (auto ih = 0; ih < h; ih++) {
auto ihPack = ih >> 2;
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihPack * cStride + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x4_t vacc0123 = vld1q_dup_f32(&initValue);
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_dup_f32(w);
// MNN_PRINT("4-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-3]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {4});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc0123 = vfmaq_f32(vacc0123, va0123, w4);
}
vacc0123 = vminq_f32(vacc0123, vmax);
vacc0123 = vmaxq_f32(vacc0123, vmin);
// how to store faster: st4 / transpose /
vst1q_lane_f32(c, vacc0123, 0);
vst1q_lane_f32(c + 4, vacc0123, 1);
vst1q_lane_f32(c + 4 * 2, vacc0123, 2);
vst1q_lane_f32(c + 4 * 3, vacc0123, 3);
}
ie += 4;
a += 4;
}
if (eSize & 0x02) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// const float* a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
for (auto ih = 0; ih < h; ih++) {
auto ihPack = ih >> 2;
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihPack * cStride + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x2_t vacc01 = vld1_dup_f32(&initValue);
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x2_t va01 = vld1_f32(a);
// __builtin_prefetch(a + aStride);
float32x2_t w2 = vld1_dup_f32(w);
// MNN_PRINT("2-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-1]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {2});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc01 = vfma_f32(vacc01, va01, w2);
}
vacc01 = vmin_f32(vacc01, vget_low_f32(vmax));
vacc01 = vmax_f32(vacc01, vget_low_f32(vmin));
// how to store faster: st4 / transpose /
vst1_lane_f32(c, vacc01, 0);
vst1_lane_f32(c + 4, vacc01, 1);
}
ie += 2;
a += 2;
}
if (eSize & 0x01) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// const float* a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
for (auto ih = 0; ih < h; ih++) {
auto ihPack = ih >> 2;
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihPack * cStride + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float acc0 = initValue;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float a0 = a[0];
const float oneW = *w++;
// MNN_PRINT("1-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0]:", ie, a - A, w - B - 1, c - C, oneW);
// formatMatrix(a, {1});
// MNN_PRINT("\n");
a = a + diff;
acc0 += a0 * oneW;
}
acc0 = std::max(std::min(maxValue, acc0), minValue);
// how to store faster: st4 / transpose /
c[0] = acc0;
}
ie += 1;
// a += 1;
}
return;
}
void MNNPackedSparseMatMulEpx4(float* C, const float* A, const float* B, size_t eSize, const size_t* parameter, const float* postParameters, const float* bias, unsigned int* NNZMap, int* dataOffsetMap) {
auto eP = parameter[0] / sizeof(float);
MNN_ASSERT((eP & 0x03) == 0); // In sparse calculate, eP should be evenly divided by 4
auto h = parameter[2];
auto l = parameter[1];
auto cStride = parameter[3] / sizeof(float);
auto hRemain = parameter[4];
auto bExtraStride = parameter[5] / sizeof(float);
// auto bStride = bExtraStride + l * 4;
auto aStride = eP * l; // sizeof(float);
auto hC4 = UP_DIV(h, 4);
float minValue = -std::numeric_limits<float>().max();
float maxValue = std::numeric_limits<float>().max();
if (nullptr != postParameters) {
minValue = postParameters[2];
maxValue = postParameters[3];
}
const float32x4_t vmin = vld1q_dup_f32(&minValue);
const float32x4_t vmax = vld1q_dup_f32(&maxValue);
const int sparseBlockOC = 4;
// MNN_PRINT("NEON MNNPackedSparseMatMul eP:%lu, eSize:%lu, l:%lu, h:%lu, cStride:%lu, aStride:%lu\n", eP, eSize, l, h, cStride, aStride);
const float* a = A;
size_t ie = 0;
for (ie = 0; ie + eP <= eSize; ie += eP) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
size_t ih = 0;
for (; ih < (h & (~0x03)); ih += sparseBlockOC) {
auto ihPack = ih >> 2;
auto c = blockC + ihPack * cStride;
// tobe merged in to weight data
float32x4_t vacc0c4 = nullptr != bias ? vld1q_f32(bias + ih) : vdupq_n_f32(0);
float32x4_t vacc1c4 = vacc0c4;
float32x4_t vacc2c4 = vacc0c4;
float32x4_t vacc3c4 = vacc0c4;
float32x4_t vacc4c4 = vacc0c4;
float32x4_t vacc5c4 = vacc0c4;
float32x4_t vacc6c4 = vacc0c4;
float32x4_t vacc7c4 = vacc0c4;
float32x4_t vacc8c4 = vacc0c4;
float32x4_t vacc9c4 = vacc0c4;
float32x4_t vacc10c4 = vacc0c4;
float32x4_t vacc11c4 = vacc0c4;
float32x4_t vacc12c4 = vacc0c4;
float32x4_t vacc13c4 = vacc0c4;
float32x4_t vacc14c4 = vacc0c4;
float32x4_t vacc15c4 = vacc0c4;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
const float32x4_t va4567 = vld1q_f32(a + 4);
const float32x4_t va89AB = vld1q_f32(a + 8);
const float32x4_t vaCDEF = vld1q_f32(a + 12);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_f32(w);
// MNN_PRINT("16-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w += 4;
a = a + diff;
vacc0c4 = vfmaq_laneq_f32(vacc0c4, w4, va0123, 0);
vacc4c4 = vfmaq_laneq_f32(vacc4c4, w4, va4567, 0);
vacc8c4 = vfmaq_laneq_f32(vacc8c4, w4, va89AB, 0);
vacc12c4 = vfmaq_laneq_f32(vacc12c4, w4, vaCDEF, 0);
vacc1c4 = vfmaq_laneq_f32(vacc1c4, w4, va0123, 1);
vacc5c4 = vfmaq_laneq_f32(vacc5c4, w4, va4567, 1);
vacc9c4 = vfmaq_laneq_f32(vacc9c4, w4, va89AB, 1);
vacc13c4 = vfmaq_laneq_f32(vacc13c4, w4, vaCDEF, 1);
vacc2c4 = vfmaq_laneq_f32(vacc2c4, w4, va0123, 2);
vacc6c4 = vfmaq_laneq_f32(vacc6c4, w4, va4567, 2);
vacc10c4 = vfmaq_laneq_f32(vacc10c4, w4, va89AB, 2);
vacc14c4 = vfmaq_laneq_f32(vacc14c4, w4, vaCDEF, 2);
vacc3c4 = vfmaq_laneq_f32(vacc3c4, w4, va0123, 3);
vacc7c4 = vfmaq_laneq_f32(vacc7c4, w4, va4567, 3);
vacc11c4 = vfmaq_laneq_f32(vacc11c4, w4, va89AB, 3);
vacc15c4 = vfmaq_laneq_f32(vacc15c4, w4, vaCDEF, 3);
}
vacc0c4 = vminq_f32(vacc0c4, vmax);
vacc1c4 = vminq_f32(vacc1c4, vmax);
vacc2c4 = vminq_f32(vacc2c4, vmax);
vacc3c4 = vminq_f32(vacc3c4, vmax);
vacc4c4 = vminq_f32(vacc4c4, vmax);
vacc5c4 = vminq_f32(vacc5c4, vmax);
vacc6c4 = vminq_f32(vacc6c4, vmax);
vacc7c4 = vminq_f32(vacc7c4, vmax);
vacc8c4 = vminq_f32(vacc8c4, vmax);
vacc9c4 = vminq_f32(vacc9c4, vmax);
vacc10c4 = vminq_f32(vacc10c4, vmax);
vacc11c4 = vminq_f32(vacc11c4, vmax);
vacc12c4 = vminq_f32(vacc12c4, vmax);
vacc13c4 = vminq_f32(vacc13c4, vmax);
vacc14c4 = vminq_f32(vacc14c4, vmax);
vacc15c4 = vminq_f32(vacc15c4, vmax);
vacc0c4 = vmaxq_f32(vacc0c4, vmin);
vacc1c4 = vmaxq_f32(vacc1c4, vmin);
vacc2c4 = vmaxq_f32(vacc2c4, vmin);
vacc3c4 = vmaxq_f32(vacc3c4, vmin);
vacc4c4 = vmaxq_f32(vacc4c4, vmin);
vacc5c4 = vmaxq_f32(vacc5c4, vmin);
vacc6c4 = vmaxq_f32(vacc6c4, vmin);
vacc7c4 = vmaxq_f32(vacc7c4, vmin);
vacc8c4 = vmaxq_f32(vacc8c4, vmin);
vacc9c4 = vmaxq_f32(vacc9c4, vmin);
vacc10c4 = vmaxq_f32(vacc10c4, vmin);
vacc11c4 = vmaxq_f32(vacc11c4, vmin);
vacc12c4 = vmaxq_f32(vacc12c4, vmin);
vacc13c4 = vmaxq_f32(vacc13c4, vmin);
vacc14c4 = vmaxq_f32(vacc14c4, vmin);
vacc15c4 = vmaxq_f32(vacc15c4, vmin);
// vacc is continuous along c
vst1q_f32(c, vacc0c4);
vst1q_f32(c + 4 , vacc1c4);
vst1q_f32(c + 4 * 2 , vacc2c4);
vst1q_f32(c + 4 * 3 , vacc3c4);
vst1q_f32(c + 4 * 4 , vacc4c4);
vst1q_f32(c + 4 * 5 , vacc5c4);
vst1q_f32(c + 4 * 6 , vacc6c4);
vst1q_f32(c + 4 * 7 , vacc7c4);
vst1q_f32(c + 4 * 8 , vacc8c4);
vst1q_f32(c + 4 * 9 , vacc9c4);
vst1q_f32(c + 4 * 10 , vacc10c4);
vst1q_f32(c + 4 * 11 , vacc11c4);
vst1q_f32(c + 4 * 12 , vacc12c4);
vst1q_f32(c + 4 * 13 , vacc13c4);
vst1q_f32(c + 4 * 14 , vacc14c4);
vst1q_f32(c + 4 * 15 , vacc15c4);
}
blockC += (h >> 2) * cStride;
for (; ih < h; ih++) {
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x4_t vacc0123 = vld1q_dup_f32(&initValue);
float32x4_t vacc4567 = vacc0123;
float32x4_t vacc89AB = vacc0123;
float32x4_t vaccCDEF = vacc0123;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
const float32x4_t va4567 = vld1q_f32(a + 4);
const float32x4_t va89AB = vld1q_f32(a + 8);
const float32x4_t vaCDEF = vld1q_f32(a + 12);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_dup_f32(w);
// MNN_PRINT("16-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc0123 = vfmaq_f32(vacc0123, va0123, w4);
vacc4567 = vfmaq_f32(vacc4567, va4567, w4);
vacc89AB = vfmaq_f32(vacc89AB, va89AB, w4);
vaccCDEF = vfmaq_f32(vaccCDEF, vaCDEF, w4);
}
vacc0123 = vminq_f32(vacc0123, vmax);
vacc4567 = vminq_f32(vacc4567, vmax);
vacc89AB = vminq_f32(vacc89AB, vmax);
vaccCDEF = vminq_f32(vaccCDEF, vmax);
vacc0123 = vmaxq_f32(vacc0123, vmin);
vacc4567 = vmaxq_f32(vacc4567, vmin);
vacc89AB = vmaxq_f32(vacc89AB, vmin);
vaccCDEF = vmaxq_f32(vaccCDEF, vmin);
// how to store faster: st4 / transpose /
vst1q_lane_f32(c, vacc0123, 0);
vst1q_lane_f32(c + 4, vacc0123, 1);
vst1q_lane_f32(c + 4 * 2, vacc0123, 2);
vst1q_lane_f32(c + 4 * 3, vacc0123, 3);
vst1q_lane_f32(c+ 4 * 4, vacc4567, 0);
vst1q_lane_f32(c + 4 * 5, vacc4567, 1);
vst1q_lane_f32(c + 4 * 6, vacc4567, 2);
vst1q_lane_f32(c + 4 * 7, vacc4567, 3);
vst1q_lane_f32(c + 4 * 8, vacc89AB, 0);
vst1q_lane_f32(c + 4 * 9, vacc89AB, 1);
vst1q_lane_f32(c + 4 * 10, vacc89AB, 2);
vst1q_lane_f32(c + 4 * 11, vacc89AB, 3);
vst1q_lane_f32(c + 4 * 12, vaccCDEF, 0);
vst1q_lane_f32(c + 4 * 13, vaccCDEF, 1);
vst1q_lane_f32(c + 4 * 14, vaccCDEF, 2);
vst1q_lane_f32(c + 4 * 15, vaccCDEF, 3);
}
a += aStride;
}
// const float* blockA = A + ie * l;
if (eSize & 0x08) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
size_t ih = 0;
for (; ih < (h & (~0x03)); ih += sparseBlockOC) {
auto ihPack = ih >> 2;
auto c = blockC + ihPack * cStride;
float32x4_t vacc0c4 = nullptr != bias ? vld1q_f32(bias + ih) : vdupq_n_f32(0.f);
float32x4_t vacc1c4 = vacc0c4;
float32x4_t vacc2c4 = vacc0c4;
float32x4_t vacc3c4 = vacc0c4;
float32x4_t vacc4c4 = vacc0c4;
float32x4_t vacc5c4 = vacc0c4;
float32x4_t vacc6c4 = vacc0c4;
float32x4_t vacc7c4 = vacc0c4;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
const float32x4_t va4567 = vld1q_f32(a + 4);
const float32x4_t va89AB = vld1q_f32(a + 8);
const float32x4_t vaCDEF = vld1q_f32(a + 12);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_f32(w);
// MNN_PRINT("16-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w += 4;
a = a + diff;
vacc0c4 = vfmaq_laneq_f32(vacc0c4, w4, va0123, 0);
vacc4c4 = vfmaq_laneq_f32(vacc4c4, w4, va4567, 0);
vacc1c4 = vfmaq_laneq_f32(vacc1c4, w4, va0123, 1);
vacc5c4 = vfmaq_laneq_f32(vacc5c4, w4, va4567, 1);
vacc2c4 = vfmaq_laneq_f32(vacc2c4, w4, va0123, 2);
vacc6c4 = vfmaq_laneq_f32(vacc6c4, w4, va4567, 2);
vacc3c4 = vfmaq_laneq_f32(vacc3c4, w4, va0123, 3);
vacc7c4 = vfmaq_laneq_f32(vacc7c4, w4, va4567, 3);
}
vacc0c4 = vminq_f32(vacc0c4, vmax);
vacc1c4 = vminq_f32(vacc1c4, vmax);
vacc2c4 = vminq_f32(vacc2c4, vmax);
vacc3c4 = vminq_f32(vacc3c4, vmax);
vacc4c4 = vminq_f32(vacc4c4, vmax);
vacc5c4 = vminq_f32(vacc5c4, vmax);
vacc6c4 = vminq_f32(vacc6c4, vmax);
vacc7c4 = vminq_f32(vacc7c4, vmax);
vacc0c4 = vmaxq_f32(vacc0c4, vmin);
vacc1c4 = vmaxq_f32(vacc1c4, vmin);
vacc2c4 = vmaxq_f32(vacc2c4, vmin);
vacc3c4 = vmaxq_f32(vacc3c4, vmin);
vacc4c4 = vmaxq_f32(vacc4c4, vmin);
vacc5c4 = vmaxq_f32(vacc5c4, vmin);
vacc6c4 = vmaxq_f32(vacc6c4, vmin);
vacc7c4 = vmaxq_f32(vacc7c4, vmin);
// vacc is continuous along c
vst1q_f32(c, vacc0c4);
vst1q_f32(c + 4 , vacc1c4);
vst1q_f32(c + 4 * 2 , vacc2c4);
vst1q_f32(c + 4 * 3 , vacc3c4);
vst1q_f32(c + 4 * 4 , vacc4c4);
vst1q_f32(c + 4 * 5 , vacc5c4);
vst1q_f32(c + 4 * 6 , vacc6c4);
vst1q_f32(c + 4 * 7 , vacc7c4);
}
blockC += (h >> 2) * cStride;
for (; ih < h; ih++) {
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x4_t vacc0123 = vld1q_dup_f32(&initValue);
float32x4_t vacc4567 = vacc0123;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
const float32x4_t va4567 = vld1q_f32(a + 4);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_dup_f32(w);
// MNN_PRINT("8-4-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-7]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {8});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc0123 = vfmaq_f32(vacc0123, va0123, w4);
vacc4567 = vfmaq_f32(vacc4567, va4567, w4);
}
vacc0123 = vminq_f32(vacc0123, vmax);
vacc4567 = vminq_f32(vacc4567, vmax);
vacc0123 = vmaxq_f32(vacc0123, vmin);
vacc4567 = vmaxq_f32(vacc4567, vmin);
// how to store faster: st4 / transpose /
vst1q_lane_f32(c, vacc0123, 0);
vst1q_lane_f32(c + 4, vacc0123, 1);
vst1q_lane_f32(c + 4 * 2, vacc0123, 2);
vst1q_lane_f32(c + 4 * 3, vacc0123, 3);
vst1q_lane_f32(c + 4 * 4, vacc4567, 0);
vst1q_lane_f32(c + 4 * 5, vacc4567, 1);
vst1q_lane_f32(c + 4 * 6, vacc4567, 2);
vst1q_lane_f32(c + 4 * 7, vacc4567, 3);
}
ie += 8;
a += 8;
}
if (eSize & 0x04) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// const float* a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
size_t ih = 0;
for (; ih < (h & (~0x03)); ih += sparseBlockOC) {
auto ihPack = ih >> 2;
auto c = blockC + ihPack * cStride;
float32x4_t vacc0c4 = nullptr != bias ? vld1q_f32(bias + ih) : vdupq_n_f32(0);
float32x4_t vacc1c4 = vacc0c4;
float32x4_t vacc2c4 = vacc0c4;
float32x4_t vacc3c4 = vacc0c4;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_f32(w);
// MNN_PRINT("4-4-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w += 4;
a = a + diff;
vacc0c4 = vfmaq_laneq_f32(vacc0c4, w4, va0123, 0);
vacc1c4 = vfmaq_laneq_f32(vacc1c4, w4, va0123, 1);
vacc2c4 = vfmaq_laneq_f32(vacc2c4, w4, va0123, 2);
vacc3c4 = vfmaq_laneq_f32(vacc3c4, w4, va0123, 3);
}
vacc0c4 = vminq_f32(vacc0c4, vmax);
vacc1c4 = vminq_f32(vacc1c4, vmax);
vacc2c4 = vminq_f32(vacc2c4, vmax);
vacc3c4 = vminq_f32(vacc3c4, vmax);
vacc0c4 = vmaxq_f32(vacc0c4, vmin);
vacc1c4 = vmaxq_f32(vacc1c4, vmin);
vacc2c4 = vmaxq_f32(vacc2c4, vmin);
vacc3c4 = vmaxq_f32(vacc3c4, vmin);
// vacc is continuous along c
vst1q_f32(c, vacc0c4);
vst1q_f32(c + 4 , vacc1c4);
vst1q_f32(c + 4 * 2 , vacc2c4);
vst1q_f32(c + 4 * 3 , vacc3c4);
}
blockC += (h >> 2) * cStride;
for (; ih < h; ih++) {
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x4_t vacc0123 = vld1q_dup_f32(&initValue);
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x4_t va0123 = vld1q_f32(a);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_dup_f32(w);
// MNN_PRINT("4-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-3]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {4});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc0123 = vfmaq_f32(vacc0123, va0123, w4);
}
vacc0123 = vminq_f32(vacc0123, vmax);
vacc0123 = vmaxq_f32(vacc0123, vmin);
// how to store faster: st4 / transpose /
vst1q_lane_f32(c, vacc0123, 0);
vst1q_lane_f32(c + 4, vacc0123, 1);
vst1q_lane_f32(c + 4 * 2, vacc0123, 2);
vst1q_lane_f32(c + 4 * 3, vacc0123, 3);
}
ie += 4;
a += 4;
}
if (eSize & 0x02) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// const float* a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
size_t ih = 0;
for (; ih < (h & (~0x03)); ih += sparseBlockOC) {
auto ihPack = ih >> 2;
auto c = blockC + ihPack * cStride;
float32x4_t vacc0c4 = nullptr != bias ? vld1q_f32(bias + ih) : vdupq_n_f32(0.f);
float32x4_t vacc1c4 = vacc0c4;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x2_t va01 = vld1_f32(a);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_f32(w);
// MNN_PRINT("2-4-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w += 4;
a = a + diff;
vacc0c4 = vfmaq_lane_f32(vacc0c4, w4, va01, 0);
vacc1c4 = vfmaq_lane_f32(vacc1c4, w4, va01, 1);
}
vacc0c4 = vminq_f32(vacc0c4, vmax);
vacc1c4 = vminq_f32(vacc1c4, vmax);
vacc0c4 = vmaxq_f32(vacc0c4, vmin);
vacc1c4 = vmaxq_f32(vacc1c4, vmin);
// vacc is continuous along c
vst1q_f32(c, vacc0c4);
vst1q_f32(c + 4, vacc1c4);
}
blockC += (h >> 2) * cStride;
for (; ih < h; ih++) {
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float32x2_t vacc01 = vld1_dup_f32(&initValue);
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x2_t va01 = vld1_f32(a);
// __builtin_prefetch(a + aStride);
float32x2_t w2 = vld1_dup_f32(w);
// MNN_PRINT("2-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-1]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {2});
// MNN_PRINT("\n");
w++;
a = a + diff;
vacc01 = vfma_f32(vacc01, va01, w2);
}
vacc01 = vmin_f32(vacc01, vget_low_f32(vmax));
vacc01 = vmax_f32(vacc01, vget_low_f32(vmin));
// how to store faster: st4 / transpose /
vst1_lane_f32(c, vacc01, 0);
vst1_lane_f32(c + 4, vacc01, 1);
}
ie += 2;
a += 2;
}
if (eSize & 0x01) {
const int* dataOffset = dataOffsetMap;
const int diff = *dataOffset++;
// const float* a = blockA + diff;
a += diff;
const float* w = B;
float* blockC = C + (ie << 2);
const unsigned int* nnz = NNZMap;
size_t ih = 0;
for (; ih < (h & (~0x03)); ih += sparseBlockOC) {
auto ihPack = ih >> 2;
auto c = blockC + ihPack * cStride;
float32x4_t vacc0c4 = nullptr != bias ? vld1q_f32(bias + ih) : vdupq_n_f32(0);
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float32x2_t va01 = vld1_f32(a);
// __builtin_prefetch(a + aStride);
float32x4_t w4 = vld1q_f32(w);
// MNN_PRINT("1-4-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0-15]:", ie, a - A, w - B, c - C, *w);
// formatMatrix(a, {16});
// MNN_PRINT("\n");
w += 4;
a = a + diff;
vacc0c4 = vfmaq_lane_f32(vacc0c4, w4, va01, 0);
}
vacc0c4 = vminq_f32(vacc0c4, vmax);
vacc0c4 = vmaxq_f32(vacc0c4, vmin);
// vacc is continuous along c
vst1q_f32(c, vacc0c4);
}
blockC += (h >> 2) * cStride;
for (; ih < h; ih++) {
auto ihSubIndex = ih & 0x03;
auto c = blockC + ihSubIndex;
const float initValue = nullptr != bias ? bias[ih] : 0;
float acc0 = initValue;
const unsigned int lElement = *nnz++;
for (auto il = 0; il < lElement; il++) {
const int diff = *dataOffset++;
const float a0 = a[0];
const float oneW = *w++;
// MNN_PRINT("1-loop: ie:%zu, a offset:%ld, w offset:%ld, c offset:%ld, w value:%f, a value[0]:", ie, a - A, w - B - 1, c - C, oneW);
// formatMatrix(a, {1});
// MNN_PRINT("\n");
a = a + diff;
acc0 += a0 * oneW;
}
acc0 = std::max(std::min(maxValue, acc0), minValue);
// how to store faster: st4 / transpose /
c[0] = acc0;
}
ie += 1;
// a += 1;
}
return;
}
#endif
void MNNGetSparseMatMulPackMode(int* eP, int *lP, int* hP) {
#ifdef __aarch64__
*eP = 16;
#else
*eP = 8; // total vector number is 16, we choose to use 8 for output.
#endif
*lP = 1;
*hP = 4;
// hp is corresponding to sparse block along right matrix colum dimension. in ramdom sparse, it is 1.
return;
}
void MNNGetMatMulPackMode(int* eP, int *lP, int* hP) {
*eP = 12;
*lP = 1;
#ifdef __aarch64__
*hP = 8;
#else
*hP = 4;
#endif
}
#ifdef __aarch64__
// input shape is (l, h) when transpose=false, else input shape is (h, l)
// output shape is (UP_DIV(h, 8), l, 8)
void MNNPackForMatMul_B(float* dest, const float* source, size_t h, size_t kernelsize, size_t ic, bool transpose) {
auto hP = (int)h / 8;
auto hR = (int)hP * 8;
auto l = kernelsize * ic;
if (hR != h) {
::memset(dest, 0, UP_DIV(h, 8)*8*l*sizeof(float));
}
if (!transpose) {
for (int y=0; y<hP; ++y) {
auto destY = dest + y * 8 * l;
auto sourceY = source + y * 8;
for (int x=0; x<l; ++x) {
::memcpy(destY + 8 * x, sourceY + x * h, 8 * sizeof(float));
}
}
auto hRemain = h - hR;
if (hRemain > 0) {
auto destY = dest + hP * 8 * l;
auto sourceY = source + hP * 8;
for (int x=0; x<l; ++x) {
::memcpy(destY + 8 * x, sourceY + x * h, hRemain * sizeof(float));
}
}
return;
}
int lC8 = (int)l / 8;
auto lR = lC8 * 8;
if (hP > 0 && lC8 > 0) {
MNNPackC8(dest, source, l, h);
}
for (int y=hR; y<h; ++y) {
auto yR = y % 8;
auto yC = hP;
for (int x=0; x<l; ++x) {
dest[x * 8 + yR + yC * 8 * l] = source[x + y * l];
}
}
for (int y=0; y<hR; ++y) {
auto yR = y % 8;
auto yC = y / 8;
for (int x=lR; x<l; ++x) {
dest[x * 8 + yR + yC * 8 * l] = source[x + y * l];
}
}
}
#else
void MNNPackForMatMul_B(float* dest, const float* source, size_t h, size_t kernelsize, size_t ic, bool transpose) {
auto l = kernelsize * ic;
if (!transpose) {
auto hP = h / 4;
auto hR = hP * 4;
if (hR != h) {
::memset(dest, 0, UP_DIV(h, 4)*4*l*sizeof(float));
}
for (int y=0; y<hP; ++y) {
auto destY = dest + y * 4 * l;
auto sourceY = source + y * 4;
for (int x=0; x<l; ++x) {
::memcpy(destY + 4 * x, sourceY + x * h, 4 * sizeof(float));
}
}
auto hRemain = h - hR;
if (hRemain > 0) {
auto destY = dest + hP * 4 * l;
auto sourceY = source + hP * 4;
for (int x=0; x<l; ++x) {
::memcpy(destY + 4 * x, sourceY + x * h, hRemain * sizeof(float));
}
}
return;
}
int offset[] = {
(int)l, (int)l
};
MNNPackC4(dest, source, l, h, offset);
}
#endif
#endif