Skip to content
Merged
14 changes: 13 additions & 1 deletion express/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1049,13 +1049,25 @@ std::vector<VARP> Variable::load(const uint8_t* buffer, size_t length) {
// Set tensor shape from net
expr->mCanDecompose = false;
}
if (nullptr != expr->get() || expr->inputType() == VARP::INPUT) {
if (nullptr != expr->get()) {
// Non-INPUT node: keep the existing behavior (replace with cloned tensor from net metadata)
for (int index = 0; index < op->outputIndexes.size(); ++index) {
auto outputIndex = op->outputIndexes[index];
delete expr->inside()->mOutputTensors[index];
expr->inside()->mOutputTensors[index] = Tensor::clone(allTensors[outputIndex].get());
Utils::copyTensorToInfo(expr->inside()->mOutputInfos.data() + index, expr->inside()->mOutputTensors[index]);
}
} else if (expr->inputType() == VARP::INPUT) {
// INPUT node: keep the tensor created by Expr::create (host already allocated,
// usage = INPUT, memoryType = MEMORY_HOST). Only migrate quantAttr if the model
// declares quantization info for this input.
for (int index = 0; index < op->outputIndexes.size(); ++index) {
auto outputIndex = op->outputIndexes[index];
auto srcTensor = allTensors[outputIndex].get();
if (nullptr != srcTensor && TensorUtils::getDescribe(srcTensor)->quantAttr) {
TensorUtils::getDescribe(expr->inside()->mOutputTensors[index])->quantAttr = TensorUtils::getDescribe(srcTensor)->quantAttr;
}
}
}

for (int index = 0; index < op->outputIndexes.size(); ++index) {
Expand Down
2 changes: 1 addition & 1 deletion source/backend/arm82/Arm82Functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2988,7 +2988,7 @@ bool Arm82Functions::init() {
gInstance->supportSDot = origin->supportSDot;
gInstance->supportI8mm = origin->supportI8mm;
gInstance->supportSME2 = origin->supportSME2;
#if defined(MNN_SME2) && defined(__aarch64__) && defined(MNN_USE_NEON)
#if defined(MNN_SME2) && defined(MNN_SUPPORT_TRANSFORMER_FUSE) && defined(__aarch64__) && defined(MNN_USE_NEON)
gInstance->supportFp16FML = origin->supportFp16FML;
#endif
gInstance->smeCoreNumber = origin->smeCoreNumber;
Expand Down
15 changes: 8 additions & 7 deletions source/backend/cpu/compute/Convolution1x1Strassen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ ErrorCode Convolution1x1Strassen::onResize(const std::vector<Tensor *> &inputs,
}
unit.offset[1] = 0;
unit.offset[2] = 0;
unit.offset[0] = core->pack * planeStart * bytes;
unit.offset[3] = core->pack * planeStart * bytes;
unit.offset[0] = static_cast<size_t>(core->pack) * planeStart * bytes;
unit.offset[3] = static_cast<size_t>(core->pack) * planeStart * bytes;
unit.mStracssenComputor.reset(new StrassenMatrixComputor(backend(), maxDepth));
int e = planeSize;
int l = ic;
Expand Down Expand Up @@ -162,19 +162,20 @@ ErrorCode Convolution1x1Strassen::onResize(const std::vector<Tensor *> &inputs,
}
auto ocStartWeight = (ocStart * core->pack) / hPack;
auto ocWeightSize = std::min(UP_DIV((ocSize * core->pack), hPack), mResource->mWeight->length(0) - ocStartWeight);
unit.offset[1] = hPack * icAlign * ocStartWeight * mWeightBytes;
unit.offset[2] = core->pack * ocStart * bytes;
// Large output projections can exceed 2 GB. Promote before multiplying to avoid int overflow.
unit.offset[1] = static_cast<size_t>(hPack) * icAlign * ocStartWeight * mWeightBytes;
unit.offset[2] = static_cast<size_t>(core->pack) * ocStart * bytes;
unit.offset[0] = 0;
unit.offset[3] = core->pack * matrixSizeE * ocStart * bytes;
unit.offset[3] = static_cast<size_t>(core->pack) * matrixSizeE * ocStart * bytes;

unit.mStracssenComputor.reset(new StrassenMatrixComputor(backend(), maxDepth));
int e = matrixSizeE;
int l = ic;
int h = std::min(ocSize * core->pack, ocWeightSize * hPack);
uint8_t* aPtr = nullptr;
auto bPtr = TensorUtils::getDescribeOrigin(mResource->mWeight.get())->mem->chunk() + hPack * icAlign * ocStartWeight * mWeightBytes;
auto bPtr = TensorUtils::getDescribeOrigin(mResource->mWeight.get())->mem->chunk() + unit.offset[1];
uint8_t* cPtr = nullptr;
auto biasPtr = TensorUtils::getDescribeOrigin(mResource->mBias.get())->mem->chunk() + core->pack * ocStart * bytes;
auto biasPtr = TensorUtils::getDescribeOrigin(mResource->mBias.get())->mem->chunk() + unit.offset[2];
memoryPool->beginGroup();
auto code = unit.mStracssenComputor->onEncode(e, l, h, matrixSizeE * core->pack, UP_DIV(l, lPack) * lPack * hPack, matrixSizeE * core->pack, aPtr, bPtr, cPtr, true, biasPtr, postParameters);
if (NO_ERROR != code) {
Expand Down
2 changes: 1 addition & 1 deletion source/backend/cpu/compute/Convolution1x1Strassen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Convolution1x1Strassen : public CPUConvolution {

struct Unit {
bool mValid = true;
int offset[4];//Input, Weight, Output, Bias
size_t offset[4]; // Input, Weight, Output, Bias
std::shared_ptr<StrassenMatrixComputor> mStracssenComputor;
};

Expand Down
10 changes: 5 additions & 5 deletions source/backend/cpu/compute/ImageProcessFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ void MNNC3ToXYZ(const unsigned char* source, unsigned char* dest, size_t count,
C3 = coeffs[r1], C4 = coeffs[4], C5 = coeffs[b1],
C6 = coeffs[r2], C7 = coeffs[7], C8 = coeffs[b2];
int sta = 0;

#if defined MNN_USE_NEON
int countD8 = (int)count / 8;
if (countD8 > 0) {
Expand All @@ -282,7 +282,7 @@ void MNNC3ToXYZ(const unsigned char* source, unsigned char* dest, size_t count,
sta = countD8 * 8;
}
#endif

for (int i = sta; i < count; ++i) {
int r = source[3 * i + 0];
int g = source[3 * i + 1];
Expand Down Expand Up @@ -333,7 +333,7 @@ void MNNC3ToBGR555(const unsigned char* source, unsigned char* dest, size_t coun
} else {
MNNRGBToBGR555Fast(source, dest, countD8);
}

i = countD8 * 8;
}
#endif
Expand Down Expand Up @@ -755,8 +755,8 @@ static void _sampleBilinearCommon(const unsigned char* source, unsigned char* de
float x = __clamp(curPoints.fX, 0, xMax);
int y0 = (int)y;
int x0 = (int)x;
int y1 = (int)ceilf(y);
int x1 = (int)ceilf(x);
int y1 = std::min((int)ceilf(y), (int)(ih - 1));
int x1 = std::min((int)ceilf(x), (int)(iw - 1));
float xF = x - (float)x0;
float yF = y - (float)y0;

Expand Down
25 changes: 12 additions & 13 deletions source/backend/cpu/riscv/rvv/MNNAccumulateSequenceNumber.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
#include <riscv_vector.h>

void MNNAccumulateSequenceNumber_RVV(float* dst, const float* src, int size) {
size_t vl = __riscv_vsetvlmax_e32m1();
vfloat32m1_t v_sum = __riscv_vfmv_v_f_f32m1(0.0f, vl);
int n = size;
for (; n > 0;) {
vl = __riscv_vsetvl_e32m1(n);
vfloat32m1_t v_src = __riscv_vle32_v_f32m1(src, vl);
v_sum = __riscv_vfadd_vv_f32m1(v_sum, v_src, vl);
n -= vl;
src += vl;
size_t vlmax = __riscv_vsetvlmax_e32m8();
vfloat32m8_t acc = __riscv_vfmv_v_f_f32m8(0.0f, vlmax);
size_t i = 0;
while (i < size) {
size_t vl = __riscv_vsetvl_e32m8(size - i);
vfloat32m8_t vs = __riscv_vle32_v_f32m8(src + i, vl);
acc = __riscv_vfadd_vv_f32m8_tu(acc, acc, vs, vl);
i += vl;
}
vl = __riscv_vsetvlmax_e32m1();
vfloat32m1_t v_total = __riscv_vfredusum_vs_f32m1_f32m1(v_sum, __riscv_vfmv_s_f_f32m1(0.0f, vl), vl);
float sum = __riscv_vfmv_f_s_f32m1_f32(v_total);
*dst = sum;
vfloat32m1_t sum = __riscv_vfmv_s_f_f32m1(0.0f, 1);
sum = __riscv_vfredusum_vs_f32m8_f32m1(acc, sum, vlmax);
*dst = __riscv_vfmv_f_s_f32m1_f32(sum);
}
1 change: 1 addition & 0 deletions source/backend/metal/ConvSimdGroupShader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3206,6 +3206,7 @@ template [[host_name("conv1x1_gemv_g4m12_wquant_sg")]] kernel kernel_type_t conv
template [[host_name("conv1x1_gemv_g4m13_wquant_sg")]] kernel kernel_type_t conv1x1_gemv_g4mx_wquant_sg<13>;
template [[host_name("conv1x1_gemv_g4m14_wquant_sg")]] kernel kernel_type_t conv1x1_gemv_g4mx_wquant_sg<14>;
template [[host_name("conv1x1_gemv_g4m15_wquant_sg")]] kernel kernel_type_t conv1x1_gemv_g4mx_wquant_sg<15>;
template [[host_name("conv1x1_gemv_g4m16_wquant_sg")]] kernel kernel_type_t conv1x1_gemv_g4mx_wquant_sg<16>;

// Fused weight+scale decode GEMV kernel: scale/bias is stored inline before each weight block
// in a single contiguous buffer, eliminating separate scale buffer access.
Expand Down
18 changes: 9 additions & 9 deletions source/backend/opencl/execution/buffer/AttentionBufExecution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,37 +330,37 @@ bool KVCacheCLManager::reallocKVCache(const KVMeta* meta, int seqlen, bool isExe
mPastLength = start;
return true;
}

size_t pastkvSize = mKvNumHead * UP_DIV(mMaxLength, 4) * mHeadDim * 4 * mByte;
size_t curMaxlen = ROUND_UP(mMaxLength, 4);
size_t pastkvSize = mKvNumHead * UP_DIV(curMaxlen, 4) * mHeadDim * 4 * mByte;
char* keyPtr = (char*)mOpenCLBackend->getOpenCLRuntime()->commandQueue().enqueueMapBuffer(
*mPastKey.get(), true, CL_MAP_READ | CL_MAP_WRITE, 0, pastkvSize, nullptr, nullptr, &res);
char* valuePtr = (char*)mOpenCLBackend->getOpenCLRuntime()->commandQueue().enqueueMapBuffer(
*mPastValue.get(), true, CL_MAP_READ | CL_MAP_WRITE, 0, pastkvSize, nullptr, nullptr, &res);

// TODO: need to ensure reserve info is sorted
auto copyDstIndex = start;
for (int n = 0; n < meta->n_reserve; ++n) {
auto begin = meta->reserve[2 * n];
auto length = meta->reserve[2 * n + 1];
// past_key : [mKvNumHead, mHeadDim, mMaxLength]
// past_value : [mKvNumHead, mMaxLength, mHeadDim]

auto copySrcIndex = start + begin;
auto copyDstIndex = start;
for (int i = 0; i < mKvNumHead * mHeadDim; i++) {
::memcpy(keyPtr + (i * mMaxLength + copyDstIndex) * mByte,
keyPtr + (i * mMaxLength + copySrcIndex) * mByte, length * mByte);
::memmove(keyPtr + (i * curMaxlen + copyDstIndex) * mByte,
keyPtr + (i * curMaxlen + copySrcIndex) * mByte, length * mByte);
}
for (int i = 0; i < mKvNumHead; i++) {
for (int j = 0; j < length; j++) {
::memcpy(valuePtr + (i * mMaxLength + copyDstIndex + j) * mHeadDim * mByte,
valuePtr + (i * mMaxLength + copySrcIndex + j) * mHeadDim * mByte, mHeadDim * mByte);
::memmove(valuePtr + (i * curMaxlen + copyDstIndex + j) * mHeadDim * mByte,
valuePtr + (i * curMaxlen + copySrcIndex + j) * mHeadDim * mByte, mHeadDim * mByte);
}
}
start += length;
copyDstIndex += length;
}
mOpenCLBackend->getOpenCLRuntime()->commandQueue().enqueueUnmapMemObject(*mPastKey.get(), keyPtr);
mOpenCLBackend->getOpenCLRuntime()->commandQueue().enqueueUnmapMemObject(*mPastValue.get(), valuePtr);
mPastLength = (int)start;
mPastLength = (int)copyDstIndex;
}
return true;
}
Expand Down
5 changes: 4 additions & 1 deletion source/core/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ static Content* loadModelFile(const char* file) {
auto net = new Content;
bool success = loader->merge(net->buffer);
if (!success) {
delete net;
return nullptr;
}
loader.reset();
Expand All @@ -105,7 +106,8 @@ Interpreter* Interpreter::createFromBuffer(const void* buffer, size_t size) {
auto net = new Content;
net->buffer.reset((int)size);
if (nullptr == net->buffer.get()) {
MNN_ERROR("Memory not enought!\n");
MNN_ERROR("Memory not enough!\n");
delete net;
return nullptr;
}
::memcpy(net->buffer.get(), buffer, size);
Expand All @@ -120,6 +122,7 @@ Interpreter* Interpreter::createFromBufferInternal(Content* net, bool enforceAut
}
auto valid = OpCommonUtils::checkNet(net->buffer.get(), net->buffer.size());
if (!valid) {
delete net;
return nullptr;
}
net->net = GetNet(net->buffer.get());
Expand Down
96 changes: 96 additions & 0 deletions test/cv/ImageProcessTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1272,3 +1272,99 @@ class ImageProcessSpeed: public MNNTestCase {
}
};
// MNNTestSuiteRegister(ImageProcessSpeed, "cv/image_process/speed");

// ========== Test: Stride Mismatch ==========
class StrideMismatchTest : public MNNTestCase {
public:
virtual bool run(int precision) {
const int W = 5, H = 5; // Non-power-of-2 to test stride alignment
const int srcChannels = 3;
const int dstChannels = 4;
// Use a wider stride (padded rows)
const int srcStride = W * srcChannels + 4; // Extra padding
std::vector<uint8_t> src(H * srcStride, 0);

// Fill valid pixel data
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
for (int c = 0; c < srcChannels; ++c) {
src[y * srcStride + x * srcChannels + c] =
static_cast<uint8_t>((y * 31 + x * 17 + c * 7) % 256);
}
}
}

// Use RGB->RGBA conversion to avoid the identity optimization path
// that skips the sampler/blitter pipeline entirely.
ImageProcess::Config config;
config.sourceFormat = RGB;
config.destFormat = RGBA;
config.filterType = MNN::CV::Filter::NEAREST;
config.wrap = CLAMP_TO_EDGE;

std::unique_ptr<ImageProcess> process(ImageProcess::create(config));
MNNTEST_ASSERT(process.get() != nullptr);

Matrix tr;
process->setMatrix(tr);

std::vector<uint8_t> dst(W * H * dstChannels, 0);
// Pass explicit stride for source; use default for output
process->convert(src.data(), W, H, srcStride, dst.data(), W, H, dstChannels, 0, halide_type_of<uint8_t>());

// Verify RGB channels match despite stride mismatch, alpha should be 255
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
for (int c = 0; c < srcChannels; ++c) {
uint8_t expected = src[y * srcStride + x * srcChannels + c];
uint8_t actual = dst[(y * W + x) * dstChannels + c];
MNNTEST_ASSERT(expected == actual);
}
// Alpha channel should be filled (typically 255)
uint8_t alpha = dst[(y * W + x) * dstChannels + 3];
MNNTEST_ASSERT(alpha == 255);
}
}
return true;
}
};
MNNTestSuiteRegister(StrideMismatchTest, "cv/image_process/stride_mismatch");

// ========== Test: Single-pixel Image ==========
class SinglePixelResizeTest : public MNNTestCase {
public:
virtual bool run(int precision) {
const int channels = 4;
uint8_t src[4] = {100, 150, 200, 255};

ImageProcess::Config config;
config.sourceFormat = RGBA;
config.destFormat = RGBA;
config.filterType = MNN::CV::Filter::BILINEAR;
config.wrap = CLAMP_TO_EDGE;

std::unique_ptr<ImageProcess> process(ImageProcess::create(config));
MNNTEST_ASSERT(process.get() != nullptr);

// Resize 1x1 -> 4x4: all output pixels should equal the source pixel
const int dstW = 4, dstH = 4;
Matrix tr;
float fx = 1.0f / dstW;
float fy = 1.0f / dstH;
tr.postScale(fx, fy);
tr.postTranslate(0.5f * (fx - 1), 0.5f * (fy - 1));
process->setMatrix(tr);

std::vector<uint8_t> dst(dstW * dstH * channels);
process->convert(src, 1, 1, 0, dst.data(), dstW, dstH, channels, 0, halide_type_of<uint8_t>());

for (int i = 0; i < dstW * dstH; ++i) {
for (int c = 0; c < channels; ++c) {
// All pixels should be the same as the single source pixel
MNNTEST_ASSERT(std::abs((int)dst[i * channels + c] - (int)src[c]) <= 1);
}
}
return true;
}
};
MNNTestSuiteRegister(SinglePixelResizeTest, "cv/image_process/single_pixel_resize");
62 changes: 62 additions & 0 deletions test/expr/LoadMapInputTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// LoadMapInputTest.cpp
// MNNTests
//
// Created by MNN on 2026/08/07.
// Copyright © 2018, Alibaba Group Holding Limited
//

#include <MNN/expr/Expr.hpp>
#include <MNN/expr/ExprCreator.hpp>
#include <MNN/expr/NeuralNetWorkOp.hpp>
#include <MNN/expr/Executor.hpp>
#include "MNNTestSuite.h"

using namespace MNN::Express;

// Regression test for #4731: Variable::loadMap input tensor lost its host buffer,
// making writeMap() return NULL and downstream format conversion crash with a
// null source pointer (e.g. expressDemo SIGSEGV on NCHW-input models).
class LoadMapInputWriteMapTest : public MNNTestCase {
public:
virtual bool run(int precision) override {
// Build a tiny NCHW-input model (Conv3x3 + ReLU) and save it.
auto x = _Input({1, 3, 8, 8}, NCHW);
std::vector<float> weight(4 * 3 * 3 * 3, 0.1f);
std::vector<float> bias(4, 0.01f);
auto w = _Const(weight.data(), {4, 3, 3, 3}, NCHW);
auto b = _Const(bias.data(), {4}, NCHW);
auto y = _Relu(_Conv(w, b, x));
Variable::save({y}, "regression_4731.mnn");

// Load the model and check the input VARP is writable.
auto varMap = Variable::loadMap("regression_4731.mnn");
auto io = Variable::getInputAndOutput(varMap);
if (io.first.empty() || io.second.empty()) {
MNN_PRINT("LoadMapInputTest: no input/output found\n");
return false;
}
auto input = io.first.begin()->second;
auto ptr = input->writeMap<float>();
if (nullptr == ptr) {
// Before the fix this was NULL (input tensor host was dropped by
// Tensor::clone in Variable::load), causing SIGSEGV downstream.
MNN_PRINT("LoadMapInputTest: writeMap returned NULL (bug #4731)\n");
return false;
}
auto inInfo = input->getInfo();
int size = 1;
for (auto d : inInfo->dim) {
size *= d;
}
for (int i = 0; i < size; ++i) {
ptr[i] = 0.5f;
}
// Forward must not crash (compute succeeds; output reading is a separate
// follow-up concern, see issue #4731).
auto output = io.second.begin()->second;
(void)output->readMap<float>();
return true;
}
};
MNNTestSuiteRegister(LoadMapInputWriteMapTest, "expr/LoadMapInputWriteMap");
Loading
Loading