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

推荐订阅源

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】推理流程脑图【1】 【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】推理流程脑图【2】
2025-04-27 · via Пусть этот камень будет более крепким, чем человек

# 前言

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

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

# 介绍

Tengine

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

# 总流程图

总流程图:

Tengine推理流程图

init_tengine()create_graph() 流程位于第一篇地址中。

# prerun_graph_multithread()

prerun_graph_multithread推理流程图

该函数是神经网络模型运行前的与运行流程。从流程图中看,其主要调用了三个函数。

# 1.infer_ir_graph_shape()

推理引擎要根据输入 tensor 的维度来推理输出 tensor 的维度。例如某个卷积节点的输入维度是【1,32,128,128】,依据 kernel 大小计算出输出维度【1,64,128,128】。所以该函数会按顺序执行所有节点,并调用每个节点的 op->infer_shape 方法(前述提到过),完成整个模型的 tensor 维度推理。

# 2.optimizer->split_graph()

切分子图。这是一个为了实现多设备运行而实现的方法,即通过切分子图实现。

当前已有一个总图,包含模型所有的节点。而设备可能包括 CPU、GPU、NPU、DNNL 等等,而这些设备可能仅支持一部分算子,这就要求将总图切分成设备支持的子图,和设备不支持的子图。设备支持的子图交给设备运行,不支持的交给 CPU 执行。

经过这个函数之后,子图就变成一各执行单元。

# 3.schedule->prerun()

预运行,其调用的是 interface->prerun() ,即前述的,和设备相关的接口中的 prerun() 函数。其又调用了三个函数:

create_exec_graph() :为每个子图创建执行子图。

通过 find_node_ops() 函数,找到节点的所有执行方式。前述卷积算法,就注册了三种执行方式。

ret = register_conv_ref_op();
    ret = register_conv_dw_hcl_x86_op();
    ret = register_conv_hcl_x86_op();

找到所有执行方式之后,调用每种执行方式的 node_ops->score() 函数,获得一个得分。例如 conv_hcl_x86_op 这种实现方式,其 score() 函数如下。它根据该节点的参数返回一个得分。

static int score(struct node_ops* node_ops, struct exec_graph* exec_graph, struct node* exec_node)
{
    struct node* ir_node = exec_node;
    struct graph* ir_graph = ir_node->graph;
    struct tensor* input_tensor = get_ir_graph_tensor(ir_graph, ir_node->input_tensors[0]);
    struct tensor* output_tensor = get_ir_graph_tensor(ir_graph, ir_node->output_tensors[0]);
    struct conv_param* param = (struct conv_param*)exec_node->op.param_mem;
    int group = param->group;
    int kernel_h = param->kernel_h;
    int kernel_w = param->kernel_w;
    int in_c = input_tensor->dims[1] / group;
    int out_c = output_tensor->dims[1] / group;
    if (input_tensor->data_type != TENGINE_DT_FP32 && input_tensor->data_type != TENGINE_DT_UINT8 && input_tensor->data_type != TENGINE_DT_INT8)
        return 0;  
    if (group != 1)
        return 0;  
    return OPS_SCORE_PREFER;   
}

得分分为以下几档:

#define OPS_SCORE_STATIC 10000
#define OPS_SCORE_BEST   8000
#define OPS_SCORE_PREFER 6000
#define OPS_SCORE_CANDO  4000
#define OPS_SCORE_NOTSUP 2000

推理引擎将选择得分最高的作为执行方法,而得分低于 4000 的方法不可使用。

下一步是调用 node_ops->init_node() 函数,是初始化节点预留的接口。

通过 prerun_exec_graph() 函数调用 node_ops->prerun() ,这个是节点预运行而预留的接口。

# run_graph()

run_graph推理流程图

到这里才开始实际执行图的每个节点。

和前述一样,还是调用和设备相关的接口 interface->run() ,先调用每个算子的 node_ops->reshape() 函数,从工程中来看该函数仍然是在做维度推理的工作,与前述的 infer_shape() 相同。似乎是重复的做法,不过不同之处在于前述的 infer_shape() 只在模型构建完成之后运行一次,而这个 reshape() 函数在算子每次执行之前都要调用。

可能是为了防止模型的每次运行,输入 tensor 大小都不一样?毕竟神经网络模型通常会不断输入数据,重复运行。(当开发者确定每次输入输入维度一致时,这里的运算就有些浪费时间了。)

然后调用算子的具体执行: node_ops->run()

# postrun_graph()

postrun_graoh推理流程图

图的后处理。

调用每个节点的后运行 node_ops->postrun()

调用子图的释放函数 release_exec_graph() ,调用每个节点的释放函数 node_ops->release_node()

# destroy_graph()

destroy_graph推理流程图

调用整个图的释放函数 interface->release_graph()

一张脑图就帮我们整体把握 Tengine 推理引擎。

# 后记

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