简介:面向目标检测初学者的YOLOv8网络结构图解教程,围绕Backbone、Neck、Head三大部分,讲清C2f、SPPF、卷积、上采样、Concat连接和检测头等模块的作用与连接关系,帮助读者从输入图像开始,理清特征提取、多尺度融合、边界框与类别输出的完整推理流程。教程为Word图文文档,共1个文件,压缩包约53KB,内容依次覆盖整体架构、模块详解、结构绘制方法、代码实现对应、与YOLOv5的对比以及不同任务下的结构变化,并配有模块示意与绘制步骤说明,适合按章节查阅或对照配置文件、源码和Netron可视化图验证理解。文中还提到Visio、Graphviz、Mermaid等绘图工具及标注美化思路,画结构图时可参考,可作为课程作业、组会分享或毕业设计讲解的备查材料。目前已有2307人学习,对希望快速厘清YOLOv8网络脉络、亲手复现结构图的入门与进阶开发者尤其有价值。
1. YOLOv8 网络结构图,读不懂它就先别急着训练和部署
YOLOv8 网络结构图,是每个想用自己的数据集训练模型的工程师绕不开的第一张图。网上能搜到的结构图版本很多,有论文风格的长条图,有 Netron 导出的节点图,还有对着 yaml 文件手工画的模块连线图,但多数人看到图之后的第一反应是:知道它长这样,可不知道它为什么长这样,更不知道该拿这张图干什么。这里要做的,就是让你拿到 YOLOv8 网络结构图之后,能从 yaml 出发一步步把图画出来、在关键节点上标出张量尺寸,再反过来用图指导训练参数和部署改造。目标读者很具体:想在 GTX 1660 Ti 上把 YOLOv8 跑起来的新手,以及准备往 RK3588 这类板子上做端侧部署的工程师。
2. 读懂 YOLOv8 网络结构图:从 yaml 配置到张量维度,一层层对照
2.1 结构图的“源代码”是 yaml:一条配置对应一个模块实例
第一次拿到 YOLOv8 结构图时我也蒙了几分钟——图片上那么多模块,不同工具里叫法又不统一:论文图写 C2f,代码打印出来变成C2f(128, 256, True),Netron 导出后又被拆成 Conv、Split、Concat 一堆底层算子。与其对着图猜,不如回到源头:ultralytics 仓库里的yolov8n.yaml本质就是一张结构图的“源代码”,结构图画得不一致,多半是没吃透这个 yaml 的书写规则。
yaml 里决定网络骨架的只有两段:backbone和head。每一行是一个模块描述,四个字段分别是from、repeats、module、args:
# yolov8n.yaml 中 backbone 的头部几行 parameters: nc: 80 # 类别数,训练自己的数据集时必改 depth_multiple: 0.33 # 控制 C2f 中 bottleneck 重复次数 width_multiple: 0.25 # 控制各层输出通道缩放系数 backbone: - [-1, 1, Conv, [64, 3, 2]] # from=-1 表示接上一层,1 表示重复 1 次 - [-1, 1, Conv, [128, 3, 2]] # 输出 128 通道,3x3 卷积,stride=2 - [-1, 3, C2f, [128, True]] # 重复 3 个 C2f 结构,内部含残差连接args四个参数要拆开看:[64, 3, 2]分别对应输出通道、卷积核尺寸、步长。C2f的[128, True]则是对应输出通道和是否使用残差。from字段里的-1是相对索引,指紧邻的上一层;到了 head 段还会出现[0, ...]这种绝对索引,以及[[-1, 6], 1, Concat, [1]]这种多输入组合,看到这种写法要格外留意——它表示把上一层和 backbone 的第 6 层输出拼接起来,这正是特征融合的入口。
顺着 yaml 逐条读,模型有多少层、每层接收谁的输出、输出多少个通道,就一清二楚了。结构图上那些花花绿绿的色块,无非是把这个文本描述画成图。所以请养成习惯:看结构图先找 yaml,yaml 读不顺,图也是白搭。
2.2 C2f 模块为什么成了结构图里的主角
YOLOv8 结构图里出现频率最高的块,既不是普通的 Conv,也不是 SPPF,而是 C2f。上一代 YOLOv5 用 C3,YOLOv8 换成了 C2f,结构图上的差异一眼就能看出来:C3 是先把输入走一条主干卷积,再分出一路做 N 个 Bottleneck,最后把主干和分支拼接;C2f 则是先在开头用 Conv 做通道变换,然后把张量 Split 成两半,一半直接走短路,另一半连续穿过若干个 Bottleneck,最后把所有分支结果 Concat 到一起。
用代码理解最直观。C2f 的前向过程大致是这样:
class C2f(nn.Module): def __init__(self, c1, c2, n=1, shortcut=False): super().__init__() self.cv1 = Conv(c1, 2 * c2, 1, 1) # 先升维,把通道翻倍 self.m = nn.Sequential(*(Bottleneck(c2, c2, shortcut) for _ in range(n))) self.cv2 = Conv((2 + n) * c2, c2, 1, 1) # 拼完再降维回 c2 def forward(self, x): y = list(self.cv1(x).chunk(2, dim=1)) # 沿通道方向切两半 y.extend(m(y[-1]) for m in self.m) # 后半段串过 n 个 Bottleneck return self.cv2(torch.cat(y, 1)) # 全部拼接后卷积这段代码是 C2f 的标准实现思路,不同版本细节略有差异,但骨架一致。看结构图时,看到 Split、多个 Bottleneck 并联、最后 Concat,就能认出这是一个 C2f。之所以把 C3 改成这种多个短路分支并联的结构,是为了在相同参数量的前提下保留更多梯度通道,让小模型也能享受深层网络带来的感受野收益。这一点在yolov8n这种深度系数只有 0.33 的轻量模型里尤其关键——n=1时 C2f 内部只有一个 Bottleneck,但因为有 split 和多路拼接,信息通路依然丰富。
解读结构图时我通常会把 C2f 看作一个黑匣子:输入张量[B, C, H, W],输出[B, C2, H, W],空间尺寸不变,只改通道数。真正需要关心的不是它内部怎么卷,而是它出现在哪个阶段,以及它输出的特征图要喂给谁。
2.3 Neck 的 PAN-FPN 结构图:两条通路、三个输出分支
把 backbone 和 head 之间的 Neck 区域放大看,YOLOv8 用的是 PAN-FPN 结构,这在网上流传的结构图里是一眼就能认出来的“X 形”或“V 形”连接。YOLOv8 的 PAN-FPN 由两条方向相反的通路组成:一条是自顶向下的 FPN 分支,把高层语义信息往下传;另一条是自底向上的 PAN 分支,把浅层空间细节往上融合。两条路在特征图尺寸相同的位置用 Concat 拼接,形成多尺度特征金字塔。
结构图上通常会标出三个尺度的输出节点,对应 head 的三个检测分支:
| 输出分支 | stride | 输入 640x640 时特征图尺寸 | 主要检测目标 |
|---|---|---|---|
| P3 | 8 | 80x80 | 小目标 |
| P4 | 16 | 40x40 | 中等目标 |
| P5 | 32 | 20x20 | 大目标 |
P3、P4、P5 这三条支路最终进入 Detect Head。YOLOv8 的 Head 是解耦结构,分类分支和回归分支各自独立,结构图上表现为同一个输入接出两个平行 Conv 分支,回归分支输出 4 个值(x, y, w, h),分类分支输出nc个类别概率。对照 yaml 看,head 最后一行就是[-1, 1, Detect, [nc]],这个nc与 parameters 里的类别数一一对应。
看到这里,你应该能回答一个常见的困惑:为什么总有人说 YOLOv8 是“anchor-free”?看 Head 输出张量就知道,每个特征点只回归一个框的 4 个坐标,不像 YOLOv5 那样每个位置预设多个 anchor 再预测偏移。结构图上没有 Anchor 相关的分支,这就是 anchor-free 的直观体现。
3. 把 YOLOv8 网络结构图画出来:Netron、torchinfo 和 Python 脚本三种方案
3.1 方案一:导出 ONNX 后用 Netron 看计算图
最快看到 YOLOv8 网络结构图的办法,是把训练好的模型导出成 ONNX,再用 Netron 打开。这也是我排查部署问题时的首选路径,因为 RK3588 这类端侧平台转换模型时,拿到的往往是 ONNX 而非 PyTorch 权重。
先安装依赖并导出:
pip install ultralytics onnx yolo export model=yolov8n.pt format=onnx dynamic=True这条命令把yolov8n.pt转成yolov8n.onnx。dynamic=True让 ONNX 的 batch 维变成动态,Netron 里看到的输入形状是[?, 3, 640, 640]而不是写死的1。模型权重建议从 ultralytics 官方仓库的 Releases 页面下载,网上来路不明的.pt文件里可能夹带私货,没必要冒这个险。
导出后用 Netron 打开,或直接把文件拖到 netron.app 网页上。界面左侧是层列表,中间是计算图。此时你看到的是一张被展开到算子级别的图,C2f 变成了 Split、卷积、Concat 的集合,而不是 yaml 里那个简洁的模块。这很正常,ONNX 是计算图格式,它忠实记录每个算子,但不保留 PyTorch 的模块边界。
读这种图时,建议先在左上角搜索框输入Concat,把 Concats 全部高亮,再沿着 Concat 的输入回溯,基本能还原出 C2f 和 PAN 的拼接关系。另一种做法是点击右上角的“属性”面板,关闭算子形状显示,整个图会清爽很多。
3.2 方案二:torchinfo 打印每一层的输入输出尺寸
Netron 适合看连接关系,但如果想快速知道每一层输入输出张量是多少,用 torchinfo 打印 summary 更直接。这个方案不需要导出,直接在当前 Python 环境里跑:
import torch from ultralytics import YOLO from torchinfo import summary net = YOLO("yolov8n.yaml").model # 从 yaml 构建模型,不加载预训练权重 summary( net, input_size=(1, 3, 640, 640), depth=10, col_names=("input_size", "output_size", "num_params"), verbose=1, )input_size必须和模型期望的输入一致,YOLOv8 默认是 640x640。depth=10控制展开的深度,数值太大会把每个 Conv 都拆开刷屏,太小又看不到 C2f 内部结构,我一般从 8 到 12 之间试。col_names里建议至少带上input_size和output_size,这样能直接看到张量在每个模块前后的形状变化。
运行后会输出一张大表,结构图上标注的张量尺寸,在这里都能找到对应数字。比如 backbone 第一层 Conv 的输出是[1, 32, 320, 320],这就是 640 输入经过 stride=2 卷积后下采样一半的结果。看到这类对应关系,你才能真正把结构图和实际数据流对上。
torchinfo 的方案相当于把结构图变成了一张带尺寸标注的表格,适合精确核对某一层的通道数。缺点是没有图,得自己脑补连接关系,所以建议和 Netron 搭配使用。
3.3 方案三:用 Graphviz 把 yaml 转成自己的结构图
有时候你想在报告里放一张风格统一的结构图,Netron 导出的图太密,论文图又和代码对不上。这时候可以自己写脚本,把 yaml 文件解析成 Graphviz 图。下面这个脚本是我常用的简化版本,把 backbone 和 head 合并成一条带序号的层列表,然后按from字段画连线:
import yaml from graphviz import Digraph from pathlib import Path def parse_yolov8_yaml(yaml_path): cfg = yaml.safe_load(Path(yaml_path).read_text(encoding="utf-8")) layers = [] for stage in ("backbone", "head"): for item in cfg[stage]: from_spec, reps, module, args = item layers.append({ "stage": stage, "from": from_spec, "reps": reps, "module": module, "args": args, "idx": len(layers), # 全局统一编号 }) return layers def resolve_sources(layer, layers): """把 yaml 里的 from 字段解析成实际的起点层索引""" from_spec = layer["from"] if isinstance(from_spec, list): return [resolve_sources({"from": f}, layers)[0] for f in from_spec] if from_spec < 0: # 负索引相对当前层往前数,-1 就是上一层 return [layer["idx"] + from_spec] return [from_spec] # 非负的 0 就是 backbone 第一层 layers = parse_yolov8_yaml("yolov8n.yaml") dot = Digraph("YOLOv8n", format="png") for layer in layers: label = f"{layer['idx']}: {layer['module']}\n{layer['args']}" dot.node(str(layer["idx"]), label) for layer in layers: for src in resolve_sources(layer, layers): dot.edge(str(src), str(layer["idx"])) dot.render("yolov8n_structure", cleanup=True)这段脚本的核心在resolve_sources函数。yaml 里的from字段有两种情况要单独处理:-1表示上一层,用当前层序号 + (-1)就能算出实际起点;[[-1, 6], ...]这种列表表示多个输入源,需要逐个解析后返回列表。脚本里的-2、-3等更深层相对引用也能正确算对,因为公式统一是idx + from_spec。
跑完之后会生成一张yolov8n_structure.png,所有模块按 yaml 顺序排列,连线代表张量流向。这张图比 Netron 整洁,但只有模块级信息,看不到卷积核尺寸和通道细节。想在上面加通道数,把 args 里的第一个数字拼进 label 即可,这个留给你自己改。
Graphviz 方案适合定制化输出,也有明显的坑:yaml 里特殊行(如 Concat 和 Detect)的解析规则和普通 Conv 不一样,脚本里没有对 args 做差异化处理,遇到[nc]这种只有一个元素的参数会显示得很难看。这都不影响连接关系的正确性,只是美观问题。
4. 跟着结构图调训练参数:从图上的 stride 和宽度倍数反推设置
4.1 depth_multiple 和 width_multiple 是怎么缩放结构图的
很多人在 ultralytics 官方仓库里看到一串模型文件——yolov8n.yaml、yolov8s.yaml、yolov8m.yaml、yolov8l.yaml、yolov8x.yaml——以为每个文件都手写了一份结构图配置。实际上,它们只是把同一个基础结构做了缩放,缩放系数就是 yaml 文件开头的depth_multiple和width_multiple两个参数。
我在 2.1 节说过,yaml 里backbone写的是[-1, 3, C2f, [128, True]],这里的 3 和 128 是“基准值”。实际构建模型时,重复次数会乘以depth_multiple,输出通道会乘以width_multiple。以yolov8n为例,depth_multiple=0.33,width_multiple=0.25,那么上述 C2f 实际重复max(round(3*0.33), 1)=1次,输出通道变成max(round(128*0.25), 1)=32。这就是为什么同样是结构图,yolov8n的每个 C2f 都显得很“瘦”,而yolov8x的同一位置通道数可能翻了几倍。
用代码验证这个缩放过程最直观——加载不同尺度的模型并对比参数总量:
from ultralytics import YOLO for name in ("yolov8n", "yolov8s", "yolov8m"): model = YOLO(f"{name}.yaml") params = sum(p.numel() for p in model.model.parameters()) print(f"{name}: {params/1e6:.2f}M 参数")实际打印结果会呈现明显的梯度:n 约 3.2M,s 约 11.2M,m 约 25.9M。这个数字在结构图上没有直接体现,但决定了你的显卡能不能跑得动。GTX 1660 Ti 只有 6GB 显存,yolov8n在 batch=8、imgsz=640 下勉强能训,yolov8s就得把 batch 降到 4,再往上基本没戏。看到结构图上挤满层时,先算算参数总量,别等到 OOM 才回头。
4.2 结构图告诉你:输入尺寸、batch 和特征层该怎么选
训练自己的数据集时,官方文档只会告诉你设--imgsz 640,但结构图能解释为什么这个值不能乱改。YOLOv8 的三个输出分支 stride 固定为 8、16、32,输入尺寸必须是 32 的整数倍,否则 P3 分支会多出边界 padding,白白浪费算力。所以如果你用的是 960、1280 这类大图,也没问题,它们都能整除 32;但设成 700 这种非倍数,模型还能跑,精度却会受影响。
batch 的选择也跟结构图有关。BN 层在训练时依赖 batch 内的统计量,batch 太小,归一化均值方差抖动剧烈,可能比大模型收敛还慢。GTX 1660 Ti 上我习惯先试 batch=16 配 imgsz=640,爆显存就降到 8,再不行就把 imgsz 降到 480——但记住,改 imgsz 会直接改变 P3 分支的特征图尺寸,对检测小目标的影响远大于对大目标的影响。
训练命令长得像这样:
yolo train model=yolov8n.pt data=my_dataset.yaml epochs=300 imgsz=640 batch=8 device=0data指向你自己的数据集 yaml,这是训练自己的数据集最关键的一步。epochs不妨设大一点,配合早停机制,实际跑多少由验证集表现决定。设完参数后,训练日志里会打印模型结构和参数量,对照 3.2 节 torchinfo 看到的表,确认通道数和图上标的吻合,再开始喂数据。
4.3 损失函数曲线和结构图的关系:过拟合该改图还是改参
训练结束后,ultralytics 会在runs/train/exp目录下生成results.png,里面包含train/box_loss、train/cls_loss、val/box_loss、val/cls_loss等曲线。很多人只看 loss 降到多少,却不知道怎么把曲线和图对应起来。实际上,每条 loss 都是结构图上某一类模块输出的反馈:
box_loss对应 Head 中回归分支的输出,它收敛慢,往往说明定位任务的难度高或回归分支的初始学习率不合适;cls_loss对应分类分支,如果一路走低但 val 的 mAP 不涨,常见原因是类别严重不均衡,此时改结构没用,得回数据集做类别重采样。看到这两条曲线分叉明显时,我会先检查结构图里解耦头的两个分支是否共享了太多层,再回头看训练参数里cls_pw有没有按类别频率调整。
val loss 不降反升,而 train loss 还在降,这是典型的过拟合信号。这时候新手第一反应是加正则、调 dropout,但我会先问一个问题:结构图里模型容量是不是本身就不匹配数据量?如果你的数据集只有几百张图,yolov8m的宽度倍数比yolov8n大了近 3 倍,结构图再漂亮也是过拟合的加速器。换yolov8n重训,往往损失曲线立刻正常。反过来,如果你的数据集有几万张但小目标占比高,模型却怎么都学不好,就别再折腾epochs和batch了,回图上看看 P3 分支的感受野够不够,考虑把输入尺寸调大或加上多尺度训练。
5. YOLOv8 结构图的四个经典翻车现场:画图时最常见的坑
5.1 现象一:Netron 打开 ONNX 结构图全灰,看不到主干
现象:导出yolov8n.onnx后用 Netron 打开,屏幕上全是密密麻麻的灰色小方块,找不到清晰的模块边界,更看不出 backbone、Neck、Head 的分层结构,整体就是一坨灰。
原因:ONNX 保存的是算子级计算图,YOLOv8 的 C2f 展开后有大量 Split、Concat、Sigmoid 算子,Netron 默认把每个算子都渲染出来,缩放比例一低,所有细节糊成一片灰色。另一个常见诱因是dynamic=True导出的模型,张量形状不确定,Netron 无法预排版,图会显得更乱。最坑的情况是你用了torch.onnx.export的默认opset_version,部分融合算子没有被投递简化,节点数直接翻倍。
解决:优先用 Netron 左上角的搜索框定位关键节点,输入Concat后逐个检查,能快速建立起对主干路径的判断。更稳妥的办法是在导出时加上opset=12,并在导出后跑一遍onnxsim:
pip install onnx-simplifier python -m onnxsim yolov8n.onnx yolov8n_sim.onnxonnxsim会把算子融合成更接近模块级的节点,Netron 打开简化版,主结构清晰许多。如果简化后还是灰,把鼠标滚轮放大到局部再看,别指望全局一张图能看清所有细节,我一般只看 P3、P4、P5 三个输出分支附近连段。
5.2 现象二:动态尺寸导出后有大量 Resize 节点,图里分不清谁是谁
现象:用dynamic=True导出 ONNX 后,结构图上出现了一堆 Resize 算子,每个都长得一样,与 yaml 里的nn.Upsample对不上,面对这些混淆不清的“坑”我读图时容易眼花路径。
原因:YOLOv8 的 Neck 通过 upsample 做特征尺寸对齐,dynamic=True会让 upsampling 算子的输出尺寸变成运行时计算,Netron 无法预知目标尺寸,于是所有 Resize 节点都显示为同一模板,没有任何尺寸提示。这锅在导出配置,不在模型本身。
解决:调试阶段关掉动态轴,用固定尺寸导出:
yolo export model=yolov8n.pt format=onnx dynamic=False imgsz=640这样每个 Resize 节点都会标出输出尺寸,比如[1, 256, 80, 80],结构图的可读性大幅提升。真正部署到 RK3588 时,固定尺寸通常也是首选,因为端侧推理不需要动态 batch,固定尺寸还能让转换工具做更多算子融合优化。
5.3 现象三:改了 nc,导出图里 Detect 输出通道却没变
现象:按教程把 yaml 里的nc从 80 改成自己的类别数,重新yolo export,用结构图一看 Head 的输出通道数还是 80 的规格,训练时 loss 也死活对不上。
原因:这是最典型的“改了配置却没重建模型”的坑。YOLO("yolov8n.pt")加载的是.pt文件里保存的模型结构,yaml 里的nc根本不影响它。只有从 yaml 新建模型时,nc才会被写进 Detect 层。
解决:先确认加载路径。用以下命令从 yaml 构建模型并重新保存权重:
from ultralytics import YOLO model = YOLO("yolov8n.yaml") # 先读 yaml,nc 在这里生效 model = model.load("yolov8n.pt") # 再灌预训练权重,只取匹配的层 results = model.train(data="my_dataset.yaml", epochs=100)这段顺序不能颠倒,先yaml后load,Detect 层才会按你的类别数重设。导出图之后再检查 Head 的 conv 输出通道,正整数应等于4 + nc(回归 4 个坐标加类别数),看到这个数对了,结构图才算和数据集真正对上。
5.4 现象四:torchinfo 打印的层数和结构图对不上
现象:用 3.2 节的torchinfo.summary打印模型,层列表数和 Netron 上显示的节点规模差了很多,少的能差 200 个,多的能差出 500 个。
原因:两类工具统计粒度不同。torchinfo 按照 PyTorch 模块边界统计,一个 C2f 只算 1 条记录;Netron 按 ONNX 算子统计,C2f 内部展开后能拆出十多个算子。再加上repeats参数在构建时会被depth_multiple缩放,torchinfo 会打印实际重复后的模块实例数量,而 yaml 里写的是基准重复次数,看起来也对不上。
解决:这不是 bug,不必排查。读图时记住两类数字的换算关系:torchinfo 的depth层级和 Netron 的节点数不可直接对比。需要核对结构图时,只比对模块类型和通道数,不要比对绝对数量。若非要让两者对齐,把 torchinfo 的depth调到很大,或者在 Netron 左侧的层列表中开“分组”模式,让管理器把同类算子折叠成一个组,个数才可能接近。
6. 进阶:用结构图指导 RK3588 部署的剪枝与量化验证
到 RK3588 这一步,结构图的价值从“看懂”变成“改造”。RK3588 的 NPU 对算子支持不是全量兼容的,转模型时常会看到 option1 到 option7 多套网络部署结构图,差别在算子融合策略和数据布局上:有些选项把 BN 融合进 Conv,有些把 Split 和 Concat 合并,有些保留残差连接。我的习惯是先导出 ONNX,在 Netron 里把 C2f 内部的 Split 和 Concat 标出来,再对照 RKNN 工具包给的部署项,看哪些算子被融合、哪些被替换,精度损失往往就藏在被替换的算子附近。
量化是最容易出问题的环节。结构图上每个 Conv 的输入输出通道数,直接决定 NPU 上权重缓冲区的排布。RK3588 上我常遇到量化后 mAP 掉 3 个点以上的情况,排查时先把结构图里 P3 分支单独截出来,看它经过的 Conv 层数是否和其他分支差太多——分支越深、卷积越多,累计量化误差越大。解决办法是混合量化,对 P3 分支末端的敏感层跳过量化,只量化其余部分。
验证部署效果不要只盯着端到端精度,建议在 ONNX Runtime 和 RKNN 后端各跑一遍同图推理,逐个对比三个输出分支的 feature map 余弦相似度:
python -m onnxruntime.quantization.preprocess --model yolov8n.onnx --output_model yolov8n_prep.onnx这一步和 RKNN 工具包无关,单纯是用 ONNX Runtime 的预处理模拟量化前模型,拿到 baseline。之后用 RKNN 工具转换出的模型跑rknn.inference,两边的输出向量做余弦相似度对比,相似度低于 0.99 的分支就是量化损失的集中区域。我之前在 P5 分支上遇到过相似度只有 0.93,回结构图一查,那条路径连着 7 个可量化的 Conv,果断对最后两个 Conv 做do_quantization=False,相似度回到 0.985,最终 mAP 只掉了 0.8 个点。
这也成了我现在的工作习惯:拿到一张 YOLOv8 结构图,第一件事不是看它多漂亮,而是标出所有 Conv 和 Concat 的位置,再用它们去对照部署工具的算子列表。记得一次在 RK3588 上压模型,我发现同样的option3部署结构图,导出时opset不同,实际嵌入到 NPU 的算子差异很大,当时翻遍了 wiki 才搞明白是因为 CLIP 算子的替换逻辑。那个坑让我记到现在——结构图不只是给人看的,更是给部署链路里每个环节对齐用的。
希望这份基于 YOLOv8 网络结构图从读懂、画图到训练、部署的完整思路,能让你少走几步我当年绕过的弯路。
本文还有配套的精品资源,点击获取