1. 线性回归
MNN 官方给的iOS Demo中,输入是图片,输出是分类结果,相对来讲,略微有些复杂,我们现在用一个最简单的线性回归模型,来说明MNN的用法。
该线性回归是y=ax+b
(其中a=2,b=0.01) ,针对该模型,我们分别进行以下实验:
- PC端的MNN python 推理
- iOS端MNN C++推理
1.1. 思路
- 确定选择后端类型(CPU、GPU、Metal,默认为CPU)及线程数量(默认值为4)
- 创建Interpreter & Session & 加载模型
- 获取输入张量及张量形状
- CPU端准备输入数据
- 将输入数据传给输入张量
- 推理
- 获取输出张量及张量形状
- CPU端获取输出数据
1.2. python推理
import MNN
import numpy as np
# 加载 MNN 模型
interpreter = MNN.Interpreter("linear_test_model.mnn")
# 创建会话
session = interpreter.createSession()
# 获取输入张量
input_tensor = interpreter.getSessionInput(session)
# 获取输入张量的形状
input_shape = input_tensor.getShape() # 使用 getShape() 获取形状
print(f"Input tensor shape: {input_shape}")
# 准备输入数据
input_data = np.random.randn(*input_shape).astype(np.float32) # 示例输入数据
print(f"Input data shape: {input_data.shape}")
input_data[0,0]=3.0
print(f"Input data: {input_data}")
print(f"理论计算值 data(y=2*x+0.01): {input_data*2+0.01}")
tmp_input = MNN.Tensor(input_shape, MNN.Halide_Type_Float, input_data, MNN.Tensor_DimensionType_Caffe)
# 设置输入并运行推理
input_tensor.copyFrom(tmp_input)
interpreter.runSession(session)
# 获取输出张量
output_tensor = interpreter.getSessionOutput(session)
output_data = output_tensor.getData() # 获取推理结果
print(f"推理输出:{output_data}")
1.3. iOS C++推理
1.3.1. Metal GPU
/**
* GPU缓存结构体,用于管理Metal GPU相关的资源
* 包含纹理缓存、设备、计算管线、常量缓冲区等Metal对象
*/
struct GpuCache {
CVMetalTextureCacheRef _textureCache; // Metal纹理缓存引用,用于管理纹理资源
id<MTLDevice> _device; // Metal设备对象,代表物理GPU
id<MTLComputePipelineState> _pretreat; // 预处理计算管线状态
id<MTLFunction> _function; // Metal着色器函数
id<MTLBuffer> _constant; // 常量缓冲区,用于存储预处理参数
id<MTLCommandQueue> _queue; // 命令队列,用于向GPU提交命令
/**
* 构造函数:初始化所有Metal相关资源
*/
GpuCache() {
// 创建默认的Metal设备(GPU)
_device = MTLCreateSystemDefaultDevice();
// 创建Metal纹理缓存
CVReturn res = CVMetalTextureCacheCreate(nil, nil, _device, nil, &_textureCache);
FUNC_PRINT(res);
// 获取默认的Metal库,包含编译好的着色器函数
id<MTLLibrary> library = [_device newDefaultLibrary];
// 获取名为"pretreat"的预处理着色器函数
_function = [library newFunctionWithName:@"pretreat"];
// 创建计算管线状态
NSError* error = nil;
_pretreat = [_device newComputePipelineStateWithFunction:_function error:&error];
// 创建常量缓冲区,大小为PretreatInfo结构体的大小
_constant = [_device newBufferWithLength:sizeof(PretreatInfo)
options:MTLCPUCacheModeDefaultCache];
// 创建命令队列
_queue = [_device newCommandQueue];
}
/**
* 析构函数:清理资源
* TODO: 需要实现资源的释放
*/
~GpuCache() {
// 这里应该添加资源释放代码
// 释放 _textureCache, _device, _pretreat, _function, _constant, _queue 等
}
};
1.3.2. GPU推理
针对GPU推理,需要配置 scheduleConfig.type = MNN_FORWARD_METAL;
及相关的GPU资源配置
接下来思路与PC端推理类似
-(void) linear_gpu {
if (!_session.running) {
std::shared_ptr<GpuCache> _cache;
// 如果GPU缓存未初始化,创建新的GPU资源缓存
if (nullptr == _cache) {
_cache.reset(new GpuCache);
}
NSLog(@"开始 GPU 推理...");
// 加载MNN模型
NSString* modelPath = [[NSBundle mainBundle] pathForResource:@"linear_test_model" ofType:@"mnn"];
auto _net = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromFile([modelPath UTF8String]));
if (!_net) {
NSLog(@"Failed to create interpreter");
return;
}
//auto session = interpreter->createSession(nullptr); // nullptr 表示使用默认配置
MNN::ScheduleConfig scheduleConfig;
scheduleConfig.type = MNN_FORWARD_METAL;
MNN::Session *_session;
if (scheduleConfig.type == MNN_FORWARD_METAL) {
// Metal GPU模式下的特殊配置
MNN::BackendConfig bnConfig;
MNNMetalSharedContext context;
// 设置Metal上下文,复用已创建的设备和命令队列
context.device = _cache->_device;
context.queue = _cache->_queue;
bnConfig.sharedContext = &context;
scheduleConfig.backendConfig = &bnConfig;
_session = _net->createSession(scheduleConfig);
}
// 获取输入张量
auto inputTensor = _net->getSessionInput(_session, NULL);
auto inputShape = inputTensor->shape(); // 获取形状
int size = 1;
for (int i = 0; i < inputShape.size(); ++i) {
size *= inputShape[i];
NSLog(@"inputShape[%d]: %d", i, inputShape[i]);
}
NSLog(@"size: %d", size);
float inputData[size];
for (int i = 0; i < size; ++i) {
inputData[i] = ((float)rand() / RAND_MAX); // 随机初始化
NSLog(@"inputData[%d]: %f", i, inputData[i]);
}
// 创建 Host Tensor 并拷贝数据
MNN::Tensor* hostTensor = MNN::Tensor::create(inputShape, halide_type_of<float>());
memcpy(hostTensor->host<float>(), inputData, sizeof(float) * size);
inputTensor->copyFromHostTensor(hostTensor);
// 运行推理
_net->runSession(_session);
// 获取输出
auto outputTensor = _net->getSessionOutput(_session, nullptr);
MNN::Tensor outputHostTensor (outputTensor);
outputTensor->copyToHostTensor(&outputHostTensor);
// 输出结果
float *outputData=outputHostTensor.host<float>();
NSLog(@"原始结果 y=2*x+0.01: %f", 2*inputData[0] + 0.01);
NSLog(@"预测输出a: %f", outputData[0]);
// 清理资源
MNN::Tensor::destroy(hostTensor);
}
}