Skip to content

深度学习笔记-MNN Session创建流程

约 1527 字大约 5 分钟

C++深度学习MNN

2025-10-23

接续上一次构建流程,在构建完成后,我们直接来看推理相关的 API ,根据 MNN 文档,目前一共有两套推理 API:

  • Session:提供会话式的推理接口
  • Module:提供表达式的推理接口

本文首先分析 Session 接口,主要聚焦在Interpreter类以上的逻辑部分。

创建一个推理会话

为了进行模型推理,用户需要按顺序创建InterpreterSession,二者分别有各自的职责:

  • Interpreter:负责模型数据的管理
  • Session:负责推理相关资源的管理

这样听起来有些模糊,首先要理清模型和推理资源的关系。

抛却 MNN 本身来谈,如果要进行一次推理,任何框架大概率都会进行如下步骤:

  1. 从文件中读取模型数据
  2. 将模型转化为计算图
  3. 进行计算

上文我们提到的“模型数据”即从文件中读取的模型数据,而“推理资源”则是转化的计算图(张量、算子等计算时数据)。

按照 MNN 文档的说法,就是:

Interpreter是模型数据的持有者;Session通过Interpreter创建,是推理数据的持有者。

我们可以查看tools/cpp/mobilenetTest.cpp了解其创建流程,为了方便阅读,笔者删去了config的初始化:

std::shared_ptr<Interpreter> net(Interpreter::createFromFile(argv[1]));

ScheduleConfig config;
Session* session = net->createSession(config);

Interpreter 的创建细节

上面的代码通过createFromFile创建了解释器,实际上,所有的createFrom族函数都只做了两件事,其一是加载Content类,其二是调用createFromBufferInternal

抛除文件加载部分,我们可以通过createFromBuffer来分析最简单的创建流程:

Interpreter* Interpreter::createFromBuffer(const void* buffer, size_t size);

其首先初始化了一个Content,然后将指针和长度赋值给它:

    auto net = new Content;
    net->buffer.reset((int)size);
    ::memcpy(net->buffer.get(), buffer, size);

最后调用createFromBufferInternal

    return createFromBufferInternal(net, true);

createFromBufferInternal的签名如下:

Interpreter* Interpreter::createFromBufferInternal(Content* net, bool enforceAuth);

虽然它有enforceAuth这个参数,但实际上没有任何作用,该函数会先检查模型有效性,再检查模型oplists的有效性。

检查模型有效性,并初始化模型:

// 检查模型有效性
auto valid = OpCommonUtils::checkNet(net->buffer.get(), net->buffer.size());

// 从 buffer 中还原网络
net->net = GetNet(net->buffer.get());

检查oplists

    // 获取 oplists ,检查其有效性
    if (nullptr == net->net->oplists()) {
        MNN_ERROR("Model has no oplist\n");
        delete net;
        return nullptr;
    }

    // 检查其中每一个 Op 的有效性
    int opSize = net->net->oplists()->size();
    for (int i = 0; i < opSize; ++i) {
        auto op = net->net->oplists()->GetAs<Op>(i);
        if (nullptr == op || nullptr == op->outputIndexes()) {
            MNN_ERROR("Invalid Model, the %d op is empty\n", i);
            delete net;
            return nullptr;
        }
    }

网络和算子

我们在编译时通过脚本生成了schema,而Netoplists相关的内容结构就是在这个时候被生成的,以Net为例,其本质是flatbuffers::Table实现的偏移量表,而获取oplists,实际上就是按照偏移量获取其某个位置的指针:

const flatbuffers::Vector<flatbuffers::Offset<Op>> *oplists() const {
  return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<Op>> *>(10);
}

Session 的创建细节

Session的创建依赖于createSession族函数,其中涉及到两个配置结构:

  • ScheduleConfig: Session的运行时信息(推理运行时类型/线程数量等)
  • BackendConfig:后端需要注意的配置信息(精度/内存策略/供电策略)

所有的Session创建流程最终都会汇集到createMultiPathSession,其签名为:

Session* Interpreter::createMultiPathSession(const std::vector<ScheduleConfig>& configs, const RuntimeInfo& runtime);

值得注意的是,Session可以将模型推导分为多个子路径,createMultiPathSession支持传入一个配置列表来创建多个推理路径,每个路径拥有不同的配置选项。

该接口的第二参数是运行时信息,其本质是一张包含了所有推理运行时的std::map

typedef std::pair<std::map<MNNForwardType, std::shared_ptr<Runtime>>,  std::shared_ptr<Runtime>> RuntimeInfo;

Runtime是推理使用的运行时实例,而MNNForwardType是推理运行时类型,其典型值为:

typedef enum {
    MNN_FORWARD_CPU = 0,
    MNN_FORWARD_AUTO = 4,
    MNN_FORWARD_CUDA = 2,
    MNN_FORWARD_OPENCL = 3,
    MNN_FORWARD_OPENGL = 6,
    MNN_FORWARD_VULKAN = 7,

    // ...
} MNNForwardType;

RuntimeInfo的第一项是所有可用的运行时列表,第二项是固定的 CPU 运行时。

运行时创建

createSession会调用createMultiPathSession的变体,其接口如下:

Session* Interpreter::createMultiPathSession(const std::vector<ScheduleConfig>& configs);

该函数会根据我们填写的配置自动创建RuntimeInfo,通过createRuntime

RuntimeInfo Interpreter::createRuntime(const std::vector<ScheduleConfig>& configs);

其实现可以总结为通过configs创建不同的运行时,并自动创建 CPU 作为备用运行时(如果之前没有创建),限于篇幅不做展开。

如果有相同类型但不同配置的运行时,后初始化的会覆盖先初始化的。

Session的创建

当创建好运行时之后,就可以继续创建Session了,createMultiPathSession的后续流程可以总结为以下部分:

  • 配置运行时(setRuntimeHint
  • 创建SessionInfo(张量和其他运行时数据的真正位置)
  • 设置会话缓存(加速部分后端初始化)
  • 创建Session
  • 写入会话缓存

我们主要看SessionInfoSession的创建。

SessionInfo的创建使用了Schedule::schedule

bool Schedule::schedule(ScheduleInfo& scheduleInfo, const Net* net, const std::vector<ScheduleConfig>& configs, const RuntimeInfo& runtimeInfo);

该函数的流程非常复杂,这里暂且把它看作一个黑盒子,其大致实现就是将模型复原为张量,并保存在info中,如果创建失败则返回false

创建好SessionInfo后,函数会初始化一个Sessionunique_ptr,其由Interpreter托管,最终返回给用户的是unique_ptr的 Handle,也就是指针本身。

auto newSession =
    std::unique_ptr<Session>(new Session(std::move(info), mNet->modes, std::move(rt)));
auto result = newSession.get();

// ...

mNet->sessions.emplace_back(std::move(newSession));

// ...

return result;

笔者在这里省略了大部分内部实现,这些部分笔者可能会放在其他专题展开分析。

后记

笔者后续还会继续分析 Session API 的其他部分实现细节,尽请期待。

更新日志

2025/10/26 09:25
查看所有更新日志
  • 4edc1-勘误:后端->运行时
  • a8e17-移除算子相关迷惑表述
  • 5a0d6-移除算子相关迷惑表述
  • 42582-fix some text
  • 942bc-rename
  • 09cd9-session create