惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

Пусть этот камень будет более крепким, чем человек

【LLM推理加速】FlashAttention 【LLM推理加速】PagedAttention 【LLM推理加速】Online Softmax LLM基础知识【1】 Transformer模型 【AI编译】LayerGroup Tiling Tile的疑惑和思考 【AI编译】深度优先的Tile调度,万事大吉? 【AI编译】多级流水线Tile调度策略 【CUDA C++】GPU内存使用【3】 【AI编译】Cache缓存地址映射 【CUDA C++】GPU存储【2】 【CUDA C++】GPU基本介绍【1】 【00】0序章-不受欢迎的来客 【转载】我来了——持续低熵 【Halide】调度优化【2】 【感想】写作进度报告5 【Halide】调度优化【1】 【转载】北大中文男足战报2 【BYOC】TVM切分子图 【转载】北大中文男足战报1 【AI编译】张量生命周期管理 SystemC 用寄存器同步建模方法 【脉动阵列】脉动阵列类型 【im2col】AScend conv accelerate 【感想】写作进度报告4 【BYOC】TVM添加自定义编译器 ccompiler 【感想】写作进度报告3 【Tengine】推理流程脑图【2】 【NCNN】学习ncnn模型转换 【编译器】使用llvm编译自定义语言【3】编译 object 【编译器】使用llvm编译自定义语言【2】转llvm IR 【编译器】使用llvm编译自定义语言【1】构建AST 【AI编译】如何进行内存分配 【感想】写作进度报告2 【AI编译】layer-group之后如何tiling 【AI编译】如何进行layer-group 【量化】连续卷积层首尾量化的可行性 【Gemm】内存对齐 【gemm】Gemm计算加速 【TVM】通过代码学习编译流程【5】FuseOps 【TVM】通过代码学习编译流程【6】CodeGen 【TVM】通过代码学习类【3.5】Pass 【TVM】通过代码学习编译流程【4】BuildRelay 【AI编译】Tiling操作能优化什么时间 【TVM】通过代码学习编译流程【3】模型编译 【TVM】通过代码学习编译流程【2】模型转换 【TVM】通过代码学习编译流程【1】必要知识 【感想】写作进度报告1 【Winograd】卷积加速算法原理及实现 SystemC 等待异步事件解决方案 【TVM】Python脚本实现模型编译和保存 【推理引擎】常见AI推理框架 【3D建模】T110E3卡迪夫蓝调皮肤模型 【TVM】C++部署运行TVM 【推理引擎】NCNN和Tengine量化推理逻辑对比 【3D建模】IS-7攻城锤流纹岩皮肤展示 【TVM】根据例子走通代码库 博客汇总目录 【Im2Col】卷积加速算法【2】NHWC 【Im2Col】卷积加速算法【1】 NCHW openBlas库的安装与简单使用 C语言工程调用Cpp库解决方案 foo Hello World
【Tengine】推理流程脑图【1】
2025-04-24 · via Пусть этот камень будет более крепким, чем человек

# 前言

本篇通过流程脑图和代码介绍 Tengine 推理引擎的推理流程。本篇是第一部分。Tengine 工程地址

作为初学者,错误在所难免,还望不吝赐教。

# 介绍

Tengine

Tengine 由 OPEN AI LAB 主导开发,该项目实现了深度学习神经网络模型在嵌入式设备上的快速、高效部署需求。为实现在众多 AIoT 应用中的跨平台部署,该项目使用 C 语言进行核心模块开发,针对嵌入式设备资源有限的特点进行了深度框架裁剪。同时采用了完全分离的前后端设计,有利于 CPU、GPU、NPU 等异构计算单元的快速移植和部署,降低评估、迁移成本。

# 推理流程

不了解 AI 推理引擎的人,可能难以理解推理引擎做了哪些工作。所以我画了一张流程图,该流程图介绍了 Tengine 推理引擎在推理神经网络的时候做了哪些事情。通过这张图,不仅能全局掌握 Tengine 的推理流程,还能对推理引擎有更深刻的认识。

直接上图:

Tengine推理流程图

图中是一大批函数名字和他们之间的链接关系。我们先从左边入手,左边红色连线分别是 init_tengine()Create_graph()prerun_graph_multithread()run_graph()get_graph_output_tensor()postrun_graoh()destroy_graph() 。这些函数名通俗易懂,我们挨个来描述他们的详细功能。

# init_tengine()

init_tengine推理流程图

顾名思义,该函数完成推理引擎的初始化。分为三个步骤:注册算子原型、注册序列化工具、注册设备。

# 1. 注册算子原型:register_all_op_prototype ()

该函数将在编译的过程中生成在 build 文件夹里。该函数将调用一百多个算子原型的注册函数。如下:

int register_all_op_prototype(){
    ...
    ret = register_argmax_op();
    ret = register_const_op();
    ret = register_convolution_op();
    ret = register_crop_op();
    ret = register_deconvolution_op();
    ...
}

以注册卷积算子为例, register_convolution_op() 函数注册了卷积算子对应的初始化函数 init_op 和释放函数 release_op

int register_convolution_op()
{
    ir_method_t m;
    m.version = 1;
    m.init = init_op;
    m.release = release_op;
    return register_op(OP_CONV, OP_CONV_NAME, &m);
}

初始化函数 init_op 为卷积参数 conv_param 开辟空间(保存 kernel shape、pad 等信息),同时注册了维度推理函数 infer_shape (根据输入 tensor 和 kernel 维度推理输出 tensor 维度)。

# 2. 注册序列化工具:register_all_serializer ()

它调用了两个函数。

1. register_tm2_serializer() ,就是注册序列化 tmfile 模型文件的工具。该工具静态结构体 tm2_serializer 如下所示,可用来读取内存,加载模型等。

static struct tm2_serializer tm2_serializer = {
    .base = {
        .get_name = get_name,
        .load_model = load_model,
        .load_mem = load_mem,
        .unload_graph = unload_graph,
        .register_op_loader = register_op_loader,
        .unregister_op_loader = unregister_op_loader,
        .init = init_tm2_serializer,
        .release = release_tm2_serializer,
    },
    .loader_list = NULL,
};

2. register_all_tm2_ops() 函数用于注册所有算子的加载工具。不同的算子参数不同,数据不同,需要注册不同的加载函数。

int register_all_tm2_ops(){
    ...
    ret = register_tm2_concat_op();
    ret = register_tm2_conv_op();
    ret = register_tm2_crop_op();
    ...
}

以卷积算子为例, register_tm2_conv_op() 函数的内容是:调用前述静态结构体 tm2_serializerregister_op_loader() 函数,将卷积算子的加载函数 tm2_load_conv 注册进去,用于模型中卷积节点的加载。

# 3. 注册设备:register_all_devices ()

作为支持多平台设备的推理引擎,设备管理也必不可少。编译阶段需要指定目标设备,我们以 CPU 为例,该函数将调用 register_cpu_device() 来注册 CPU 设备。

cpu_device 是个静态结构体,其包含了接口 interface 、分配器 allocator 、优化器 optimizer

static struct cpu_device cpu_dev = {
    .base = {
        .name = CPU_DEVICE_NAME,
        .interface = &cpu_interface,
        .allocator = &cpu_allocator,
        .optimizer = &cpu_optimizer,
        .scheduler = NULL,
        .privacy = NULL,
    },
    .master_cpu = 0,
    .cpu_model = 0,
};

着重看一下 CPU 设备的接口,接口 cpu_interface 也是一个静态结构体:

static struct interface cpu_interface = {
    .init = init_cpu,
    .pre_run = prerun,
    .run = run,
    .post_run = postrun,
    .async_run = NULL,
    .async_wait = NULL,
    .release_graph = cpu_dev_release_exec_graph,
    .release_device = release_cpu,
};

接口包含 CPU 设备初始化 init 、预运行 pre_run 、运行 run 、后运行 post_run

当前会调用 CPU 设备初始化 init ,其余的会在后续调用到。

CPU 设备初始化函数 init_cpu 会调用 register_all_cpu_ops() 函数,来注册所有 CPU 算子实现。

int register_all_cpu_ops(){
    ...
    ret = register_concat_ref_op();
    ret = register_conv_ref_op();
    ret = register_conv_dw_hcl_x86_op();
    ret = register_conv_hcl_x86_op();
    ret = register_crop_ref_op();
    ...
}

从这段代码中看到,卷积算子的推理方式注册了三个。这是因为即使同一个设备的同一个算子,也可能有不同的实现方法,有些推理方法速度更快,但是有限制条件。所以具体使用哪个方法,会在后续流程中介绍。

# Create_graph()

Create_graph推理流程图

该函数完成模型图结构的创建过程。

# 1. 创建上下文:create_context ()

Context 是图执行的上下文,它会绑定调度器、设备等信息。这里不过多解释。

typedef struct context
{
    char* name;
    struct scheduler* scheduler; 
    struct device* device;       
    void* default_options;       
    void* device_options;        
} ir_context_t;

# 2. 创建图:create_ir_graph ()

该函数主要创建一个新的图,并调用 init_ir_graph() 初始化一些信息,如图的节点数量、tensor 数量、输入输出等,做的事情相当有限。

# 3. 加载图结构:load_mem ()

通过 find_serializer_via_name() 函数找到前述的序列化工具 serializer ,调用其
运行序列化工具的 load_mem() 函数,解析 tmfile 文件。其中 load_graph 函数会依次调用加载 tensor load_graph_tensors() 、加载节点 load_graph_nodes() 、加载输入输出节点 load_graph_io_nodes() 、加载子图信息 load_graph_sub_info()

后半部分的流程介绍将在下一篇完成。

# 后记

本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,并指明哪一篇博客,我看到一定及时回复!