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

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
V
V2EX
美团技术团队
H
Help Net Security
月光博客
月光博客
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
The Cloudflare Blog
U
Unit 42
大猫的无限游戏
大猫的无限游戏
Recent Announcements
Recent Announcements
A
About on SuperTechFans
博客园 - Franky
The GitHub Blog
The GitHub Blog
N
Netflix TechBlog - Medium
人人都是产品经理
人人都是产品经理
博客园 - 司徒正美
MyScale Blog
MyScale Blog
B
Blog
雷峰网
雷峰网
Y
Y Combinator Blog
云风的 BLOG
云风的 BLOG
T
The Blog of Author Tim Ferriss

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

【MLIR】Transform Dialect【0】保持计算结构 【MLIR】跟着Toy语言学习MLIR【6】支持复合结构类型 【MLIR】跟着Toy语言学习MLIR【5】降级到LLVM及CodeGen 【MLIR】跟着Toy语言学习MLIR【4】部分降级到低层方言 【MLIR】跟着Toy语言学习MLIR【3】通过接口实现通用转换 【MLIR】跟着Toy语言学习MLIR【2】pattern匹配和重写 【MLIR】跟着Toy语言学习MLIR【1】Toy语言和 Toy Dialect 【琐记】你的爱好能坚持多少年 【琐记】烟火与尘埃 【Triton】Triton实现矩阵乘 【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
【Tengine】推理流程脑图【1】
Post author: XianMu@Пусть этот камень будет более крепким, чем ч · 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,并指明哪一篇博客,我看到一定及时回复!