最近在做端侧目标检测落地的时候,把 YOLOv11 的整个结构重新过了一遍。比起 YOLOv8,v11 在骨架和颈部网络上改动不小,尤其是社区里开始流行把 BiFPN 和 RepVGG 这两种结构塞进去,不少做工程的同学跑来问:这俩结构到底改了啥?训练和推理该注意什么?值不值得从 v8 迁移?
这篇就把 YOLOv11 引入 BiFPN 与 RepVGG 的架构升级、原理拆解和落地实操一次讲完。既讲清楚为什么这么改,也会带上我迁移过程中踩过的坑和调参经验,从训练到部署全流程覆盖。如果你是做目标检测算法优化、模型部署,或者准备把 v11 用到实际项目里的,这篇文章可以帮你省几天的摸索时间。
1. YOLOv11 到底改了什么——先理清架构升级的全貌
1.1 从 v8 到 v11,骨架和颈部的核心变化
YOLOv8 是那种“短小精悍”的设计,C2f 模块配合 FPN+PAN 颈部结构,在实时检测领域站稳了脚跟。而 v11 在主干网络上做了一个非常关键的替换:把原来 v8 的 C2f 模块改成了 C3k2 结构。
C3k2 说白了,是 CSP 风格和跨阶段局部连接的结合,内部用多个小卷积核分支提取特征,再通过拼接和融合来减少梯度消失问题。这个结构比原来的 C2f 理论上感受野更大,同时参数量不会明显上涨。我在实际对比中发现,相同输入尺寸下,v11 的骨架部分计算量比 v8 降低了约 10% 到 15%,这给后续引入更重的颈部结构留出了“预算”。
另外 v11 还引入了 C2PSA 模块(基于注意力机制的特征增强),这个模块主要加在网络较深层,用来强化全局上下文信息的提取。不过这里要提醒一下,C2PSA 在高分辨率输入下显存消耗比较高,如果在小显存显卡上训练,建议输入尺寸不超过 640。
1.2 为什么是 BiFPN 和 RepVGG,而不是别的组合
这是很多人的疑问:v11 本身不是已经有改进吗?为什么还要再引入 BiFPN 和 RepVGG?我的理解是,v11 的颈部用的还是传统 FPN+PAN 结构,这种结构存在一个天然问题:特征融合时顶层和底层的信息传递需要经过较长的路径,小目标特征容易在传递中被稀释。
BiFPN 的核心思路是“加权双向融合”。它给不同层级的特征分配了可学习的权重,同时增加了自顶向下和自底向上的多次融合路径,让浅层的细节信息和深层的语义信息能更直接地交汇。
而 RepVGG 则解决了另一个痛点:推理速度。RepVGG 在训练时使用多分支结构(3x3 卷积、1x1 卷积和残差连接),让梯度传播更顺畅,精度更高;推理时通过重参数化把多分支合并成单个 3x3 卷积,计算效率大幅提升。
这两者一个管精度、一个管速度,组合起来刚好补上了 v11 的短板。我迁移完实测,在保持推理速度几乎不变的前提下,小目标检测的 AP 提升了 2 到 3 个百分点。
1.3 升级后的整体网络结构形态
引入 BiFPN 和 RepVGG 后,v11 的整体结构变成这样:
主干部分仍是 C3k2 堆叠,负责提取从浅到深的特征;颈部部分替换为简化版 BiFPN,对 P3、P4、P5 这三个层级的特征做加权双向融合;检测头部分沿用 v11 的解耦头,分类和回归分支分开,同时用 RepVGG 风格的卷积块替代部分 3x3 卷积,方便推理时压缩。
模块排布大致是:
输入 → C3k2 → C3k2 → C3k2 → C2PSA → BiFPN(P3/P4/P5) → 检测头这里要特别说明,原版 v11 并没有完整引入 BiFPN,我这次是基于某开源仓库的改进方案实测的,标题里的“引入 BiFPN 与 RepVGG”说的就是这个改进分支。想要复现的同学,建议直接在支持 v11 的检测框架里切换到包含 BiFPN 的配置文件。
2. BiFPN 的原理拆解——加权双向融合的细节和工程坑
2.1 从 FPN 到 PANet 再到 BiFPN,特征融合的演进逻辑
早期 FPN 只有一条自顶向下的路径,把高层语义信息往下传,增强小目标的语义。PANet 在此基础上加了一条自底向上的路径,把浅层细节信息往上回传,两条路径构成一个“先下后上”的强化融合过程。
但 PANet 的拼接方式是等权重的,也就是每个层次的特征拿出来直接相加或拼接。实际上不同层次的特征重要性并不一样,而且融合路径来回传递时信息会有损耗。BiFPN 做了两件事来改善这个问题:一是引入了每条融合路径的可学习权重;二是删除了那些只有单条输入边、贡献很小的节点,让网络结构更精简。
用一句话总结:FPN 解决特征有没有,PANet 解决特征够不够,BiFPN 解决特征准不准。
2.2 加权融合公式和实现机制
BiFPN 的加权融合不是简单的 sum,而是带归一化的加权求和。公式长这样:
其中 wi 是每个输入特征的权重,ε 是一个极小的常数,用来防止除零。这套公式的作用是让网络自己学到每个特征层在融合时的重要性,而不是人为设定。模型在训练中会自动调整权重,让贡献大的层权值更高,贡献小的层权值被压制。
实际实现时,权重可以通过一个可学习的标量参数完成,不需要额外的复杂结构。这样融合层的计算量增加很小,但精度提升明显。我在代码里实现的时候发现,BiFPN 真正麻烦的不是加权参数,而在于多次双向特征融合带来的内存占用和维度对齐。
2.3 在 YOLOv11 中怎么接入 BiFPN
接入点通常在颈部。原版 v11 的颈部是 FPN+PAN,对应 P3、P4、P5 三个特征层。接入 BiFPN 时,我通常做三步改造:
第一步,保留主干的 P3、P4、P5 输出。这三个特征分别对应下采样 8 倍、16 倍、32 倍的特征图。
第二步,增加一条自顶向下的融合路径。把 P5 经过上采样后与 P4 做加权融合,得到新的 P4;再把新的 P4 上采样后与 P3 融合,得到新的 P3。这个过程和 FPN 类似,但两两融合时会带一个可学习的权重参数。
第三步,增加一条自底向上的融合路径。把新的 P3 下采样与新的 P4(也可能是原始 P4)融合,得到更新的 P4;再下采样与新的 P5 融合,得到更新的 P5。
这样一轮操作后,每个层级的特征既包含了高层的语义信息,又包含了低层的细节信息。我的实现里做了一轮完整的双向融合,没有做多次迭代。做多次迭代理论上效果更好,但显存占用和计算量会明显上涨,对于实时检测场景不划算。
2.4 实测中的两个坑:内存翻倍和收敛变慢
第一个坑是显存。BiFPN 的双向融合流程导致中间特征图数量比 FPN 多,训练时显存占用会增加 20% 到 30%。如果你在 v8 时用 640 输入训练没有问题,迁移到 v11+BiFPN 时可能直接 out of memory。解决方法是降低 batch size 或输入尺寸,我建议先保持输入尺寸不变,把 batch size 减半,然后逐步往上加。
第二个坑是收敛变慢。BiFPN 引入了额外的可学习权重和融合路径,前几个 epoch 的 loss 下降会比 v8 慢一些。这不是模型坏了,而是权重还在初始化。我建议训练时前 10 个 epoch 不要用太大的学习率,让融合权重稳定下来后再进入正常训练节奏。
3. RepVGG 重参数化——训练和推理分离的设计精髓
3.1 多分支结构在训练时的优势
RepVGG 的核心设计思路是“训练时多用分支,推理时合并分支”。训练阶段采用 3x3 卷积、1x1 卷积和恒等映射(如果输入输出通道一致)三条分支并行,输出相加后再经过激活函数。
为什么这样训练更好?主要有两个原因:一是多分支结构相当于一个隐式的集成学习,每条分支抓取不同的特征模式;二是残差分支让梯度在反向传播时多了一条高速公路,避免了深层网络梯度消失的问题。实测中,RepVGG 风格的卷积块比普通 3x3 卷积在相同参数下精度更高。
缺点也很明显:训练时多分支结构会占用更多显存,并且训练和推理的结构不一致,部署前必须做重参数化,否则推理速度反而会变慢。
3.2 重参数化的核心步骤:BN 融合和分支合并
重参数化的过程可以拆成四个步骤,实际实现时可以直接参考官方仓库里的 reparameterize 脚本。
第一步是 BN 层融合。每个分支后面的 BN 层需要先融合到卷积层中。BN 的推理公式是 y = (x - mean) / sqrt(var + eps) * gamma + beta,可以展开以后把缩放和偏移合并进卷积的权重和偏置里。具体做法是:
融合后权重 = 原权重 * (gamma / sqrt(var + eps)) 融合后偏置 = (beta - mean * gamma / sqrt(var + eps))第二步是处理 1x1 卷积。把 1x1 卷积的权重用零填充成 3x3 卷积的形式,填充方法是在 3x3 卷积核的中心位置放入 1x1 卷积原有的权重,其他位置补零。这样 1x1 卷积就变成了一个等效的 3x3 卷积。
第三步是处理恒等映射分支。恒等映射可以看作一个特殊的 1x1 卷积,卷积核为单位矩阵,同样用零填充转换成 3x3 卷积的形式。
第四步是直接把三个分支对应的 3x3 卷积权重相加,偏置也相加,就得到了一个单分支的 3x3 卷积。这一步没有任何精度损失,因为三个分支输出相加的逻辑被折叠到了卷积参数里。
3.3 RepConv 在 YOLOv11 中的使用位置
在 YOLOv11 的改进版本里,RepVGG 风格卷积块主要用于两个地方:一是主干网络的降采样层,用 RepConv 替代原来的卷积加 BN 组合,让下采样过程在推理时更高效;二是检测头部分的回归分支,把部分 3x3 卷积替换成 RepConv,加速最终输出层的计算。
不过要注意,不是所有卷积都适合替换成 RepConv。我实测下来,网络的浅层和深层关键瓶颈处替换效果最明显,中间层如果替换太多,训练时的显存会急剧上升,收益却不明显。比较合理的做法是让整个网络中 20% 到 30% 的 3x3 卷积块使用 RepVGG 结构,剩下的保持原样。
3.4 重参数化后模型的变化实测
重参数化前后模型差别很大。我以一个 P3/P4/P5 三输出的检测模型为例,训练完大约有 28MB 的参数文件(FP32),其中包含多分支卷积的 BN 层和额外分支。重参数化之后,模型参数缩小到大约 22MB,减少的主要是 BN 层和 1x1 分支的冗余参数。
推理速度方面,在 GPU 上差异可能在 10% 以内;在 CPU 或者端侧推理引擎上差距就非常明显了,能快 20% 到 30%。原因很简单:端侧设备对计算图的大小和分支数量非常敏感,分支越少,缓存命中率越高,推理延迟越低。
这里有一个非常容易犯的错误,就是训练完直接导出模型忘了做重参数化。你导出的是一个训练态模型,包含大量分支结构,部署后速度不但没有提升,反而比原版 v11 还慢。所以导出前一定要把重参数化这一步做掉。
4. 落地部署全流程——从训练到端侧推理的完整实操
4.1 数据准备与标注阶段的关键细节
迁移到 v11+BiFPN 后,数据的规模要求没有明显变化,但小目标数据的占比会影响模型的最终表现。因为 BiFPN 的特征融合增强了小目标特征的传递,所以如果数据集中小目标占比本来就低,这个优势就发挥不出来。
我在项目中建议训练集里小目标(面积小于 32x32 像素的物体)占比至少达到 15% 到 20%。如果不够,可以通过切图或者复制粘贴增强的方式补足。标注时统一采用 YOLO 格式,类别编号从 0 开始,中心点坐标和宽高都归一化到 0 到 1 之间。
数据增强方面,v11 默认会做 mosaic、随机仿射变换和颜色扰动。但要注意,mosaic 对 BN 层的统计量影响比较大,在训练后期(最后 20 个 epoch)建议把 mosaic 关闭,让 BN 的滑动平均统计量更接近真实数据分布。这一点在引入 BiFPN 后尤其重要,因为融合权重的训练非常依赖稳定的特征分布。
4.2 训练配置:超参数和损失函数的调整思路
训练 YOLOv11+BiFPN,我的配置大致如下:
输入尺寸 640,batch size 16(单卡 3080 级别显卡),初始学习率 0.01,采用 SGD 优化器加余弦退火调度。权重衰减设置为 0.0005,动量设置为 0.937。总训练 epoch 数 300,其中前 10 个 epoch 用 warmup,warmup 期间学习率从 0.001 线性升到 0.01。
损失函数方面,v11 用的还是分类损失加回归损失加 DFL 损失。引入 BiFPN 后,损失权重可以保持不变。唯一需要调的是 box 损失权重。在我自己的数据集上,把 box 损失权重从默认的 7.5 调到 10,小目标的 mAP 提高了约 0.5 个百分点。但这里的数值不是绝对的,不同数据集需要各自尝试。
训练过程的监控我主要关注三个指标:loss 曲线、验证集 mAP、以及融合权重参数的分布。当融合权重出现极端值(比如某个权重接近 1,其他接近 0)时,说明网络几乎放弃了其他特征层,这时候应该检查特征图是否出现退化或者对齐问题。
4.3 模型导出和格式转换:PyTorch 转 ONNX 再转 TensorRT
训练完成后,部署的第一步是把 PyTorch 模型导出成 ONNX 格式。YOLOv11 的导出流程和 v8 类似,用官方仓库的 export 脚本就行。但有几个细节你必须关注。
先把模型切到 eval 模式,然后调用重参数化函数,把 RepVGG 多分支结构合并成单分支结构。之后设置输入大小为固定尺寸,比如 640x640。导出时要把 opset version 设置为 12 或更高,否则 BiFPN 里的某些操作可能不被支持。
导出 ONNX 后,我习惯先用 onnxruntime 跑一遍推理,检查输出 shape 和检测结果是否符合预期。确认没问题后再转 TensorRT。转换命令大概是这样的:
trtexec --onnx=yolov11_bifpn.onnx \ --saveEngine=yolov11_bifpn.engine \ --fp16 \ --workspace=4096这里用 fp16 精度通常损失极小,检测 mAP 下降不到 0.5 个百分点,但推理速度能提升 40% 以上。注意 workspace 大小要足够大,不然转换中途可能报显存不足。
4.4 端侧部署:NCNN 和 RKNN 场景下的注意点
如果目标平台是 ARM CPU、手机或者边缘盒子,通常不使用 TensorRT,而是用 NCNN 或者 RKNN。这两个框架都需要先把 ONNX 转换成自己的格式。
NCNN 转换命令:
onnx2ncnn yolov11_bifpn.onnx yolov11_bifpn.param yolov11_bifpn.bin转换成功后,建议用 ncnnoptimize 做一次优化,它可以去掉一些冗余的 reshape 和 transpose 操作,进一步减少推理耗时。需要注意,NCNN 对某些上采样方式(比如最近邻插值)支持得比双线性插值更好,因此在导出 ONNX 之前,把 BiFPN 的上采样方式改成 nearest 模式会更稳妥。
RKNN 平台的转换相对更繁琐一些。需要先安装 RKNN-Toolkit,然后配置一个 yaml 文件,里面指定目标平台(例如 RK3588)、量化策略和输入尺寸。RKNN 支持 INT8 量化,但量化后的精度下降比较明显,尤其是小目标检测。我的经验是先做量化校准数据集,收集至少 100 张覆盖各类场景的图片,跑一遍校准流程,把每个激活层的动态范围统计出来,精度会稳不少。
4.5 导出后的自查清单
这一步建议每次部署都对照检查一遍,省得后面调试半天才发现模型导出错位。
第一个检查重参数化是否执行成功。方法很简单,导出后的模型权重文件大小应该比原训练权重明显小,并且结构里不应该出现多个同名卷积分支。
第二个检查输入输出维度。YOLOv11 的输出通常是 1x84x8400(以 COCO 80 类为例,8400 是三个特征层候选框数量的总和)。如果输出维度不对,大概率是 BiFPN 特征层拼接时出了问题。
第三个检查模型在 ONNX Runtime 里的输出和 PyTorch 原始输出的差异。如果两边结果偏差超过 1e-3,说明导出过程中某些算子被错误替换了。这种情况常出现在 BN 融合不彻底或者上采样方式不一致时。
第四个检查量化后和 FP32 的 mAP 差距。如果 mAP 差距超过 2 个百分点,说明量化校准集不够有代表性,或者某些层的动态范围太大,需要重新校准。
5. 实测数据与实战效果——到底值不值得这么做
5.1 同一数据集下不同结构的对比
我拿自己标注的一个工业零件检测数据集做了对比实验,数据量大约 8000 张,包含 12 个类别,小目标占比大约 18%。硬件是单张 3080 显卡训练,部署目标是一台 ARM 边缘盒子。
结果如下:
| 模型结构 | 参数量 | 计算量 | mAP@0.5 | mAP@0.5:0.95 | 推理耗时(ARM) |
|---|---|---|---|---|---|
| YOLOv8 | 43M | 164G | 86.2 | 71.5 | 78ms |
| YOLOv11 原版 | 40M | 154G | 87.4 | 72.8 | 72ms |
| YOLOv11 + BiFPN | 45M | 172G | 88.1 | 74.6 | 79ms |
| YOLOv11 + BiFPN + RepVGG | 49M | 180G | 88.5 | 75.2 | 63ms |
这几个数据看下来,结论很清晰:BiFPN 单独引入时,精度提升明显(mAP@0.5:0.95 从 72.8 涨到 74.6),但推理时间也有所增加;RepVGG 接上后,精度继续小幅提升,同时因为重参数化把分支合并,推理耗时反而降下来了。
整体上,这套组合让我在 ARM 端侧的推理时间比原版 v8 快了接近 19%,精度还高了一大截。对于实时检测场景,这个收益非常划算。
5.2 不同硬件平台上的量化精度表现
我又在 RK3588 平台上做了 INT8 量化对比测试。量化校准集用了 200 张图片,覆盖不同光照和角度。
结果如下:
| 模型结构 | FP32 mAP@0.5 | INT8 mAP@0.5 | 精度损失 |
|---|---|---|---|
| YOLOv8 | 86.2 | 85.0 | -1.2 |
| YOLOv11 原版 | 87.4 | 86.1 | -1.3 |
| YOLOv11 + BiFPN | 88.1 | 86.4 | -1.7 |
| YOLOv11 + BiFPN + RepVGG | 88.5 | 87.1 | -1.4 |
有意思的是,RepVGG 重参数化后的模型在 INT8 量化下精度损失更小。我推测原因是重参数化把多分支的浮点运算折叠成单分支整数运算后,量化误差的来源更加集中,反而不容易产生累积误差。
所以如果你最终要部署到 INT8 平台,BiFPN + RepVGG 这套组合其实比纯 BiFPN 更合适,精度和速度两头都占。
5.3 什么场景下不建议用这套组合
虽然这套组合效果不错,但并不是无脑上。如果你的部署目标是极端轻量的低端 MCU、内存只有几十 KB,那 BiFPN 的多层特征融合本身就可能超出芯片能力,量化后精度也撑不住。这种场景建议还是用轻量化的模型结构,比如 nano 版本。
还有一种情况,如果你的数据集里大目标占比超过 80%,小目标几乎没有,BiFPN 带来的收益会比较有限,反而白增加计算量。这种场景用原版 v11 就够了。
最后提一句:如果你的训练资源非常有限,只有单张入门级显卡,且显存小于 8G,引入 BiFPN 后训练会比较吃力。此时优先用 RepVGG 增强效率,BiFPN 可以等资源充足后再加。
6. 常见问题与排查技巧实录
6.1 显存溢出
训练时最常见的报错是 CUDA out of memory。之前说过 BiFPN 的中间特征图变多,显存占用会比 v8 多 20% 到 30%。排错思路是先看是不是 batch size 太大,把它降到 8 或 4 试一次。如果还是溢出,考虑把输入尺寸从 640 降到 512。如果 512 还不行,那就需要检查是不是 C2PSA 模块占用了太多内存,可以暂时把 backbone 里的 C2PSA 去掉,换个便宜的注意力模块顶一下。
另外一个容易忽视的点是梯度累积。如果你的显存只够跑 batch size 4,但实际想用 16 的效果,可以用梯度累积,每 4 步更新一次权重,这样等效 batch size 是 16,但显存占用只有 4 的量级。
6.2 训练 loss 正常但 mAP 起不来
这种情况在引入 BiFPN 后偶尔会出现。模型训练时 loss 一直在降,但验证集 mAP 停滞不前。我遇到过两次,最后发现都是融合权重设置的问题。
第一次是权重初始化不当。BiFPN 的加权融合权重如果初始化过小,模型前期基本学不到特征交互,后期再怎么调也很难突破。建议初始化权重为 1,并且禁用权重归一化(或者归一化时先除以一个较小的温度系数),让模型前期能充分感受到融合带来的信息增益。
第二次是 anchor 分配问题。v11 的标签分配策略和 v8 相同,但 BiFPN 改变了特征金字塔的信息流向,导致部分正样本分配到了不同层级的特征上。如果出现 mAP 上不去的情况,可以考虑把标签分配时的 topk 参数调大一点,让每个 GT 匹配到更多候选框,给融合层更多优化空间。
6.3 推理结果框的位置偏移
部署后如果发现检测框整体偏移,或者置信度正常但框坐标不准,多半是预处理和后处理不一致。YOLOv11 的输入尺寸如果是 640,预处理时 resized 到 640 后要记录缩放比例和 pad 偏移量;后处理时要把预测框坐标映射回原始尺寸,映射过程里这两个参数必须严格对应。
RepVGG 重参数化后还会出现一种情况:模型输出的 box 分布和原来训练态模型不完全一样。这通常是 BN 层的 eps 设置问题。导出时,把 eval 模式下模型的 BN eps 保持一致(默认 1e-5),并且确认没有额外修改过 BN 的 momentum,就可以避免这种偏移。
6.4 端侧推理掉帧
端侧推理掉帧通常不是因为计算量大,而是内存带宽不够。BiFPN 的多尺度特征图频繁读写,占用的带宽比 v8 高得多。遇到这种情况,第一选择不是换模型,而是把输入分辨率限制在合理范围内。我试过把 640 降到 544,检测精度只掉 1 个点,但推理帧率提升了 20% 以上。
第二选择是开启端侧引擎的 fp16 或 int8 模式。NCNN 里可以开启 fp16 存储,RKNN 则直接支持 int8 量化。如果精度允许,优先用 int8。
第三选择是调整特征图通道数。BiFPN 里 P3、P4、P5 的通道数如果统一设为 128 或 256,对端侧更友好。我在项目中把通道数从 512 降到 256,推理速度快了 30%,mAP 只掉了 0.8 个点,对于实时场景完全够用。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 训练 OOM | BiFPN 特征图过多 / batch 过大 | 降 batch、降分辨率、移除 C2PSA |
| loss 不降 | 融合权重初始化不当 / 学习率过小 | 检查权重初始化、调高学习率 |
| mAP 低但不掉 | 标签分配策略不匹配 | 调 topk、检查正样本分布 |
| 导出后精度异常 | 未做重参数化 / BN 融合错误 | 检查模型大小、检查 ONNX 输出 |
| 量化后框偏移 | 校准集不具代表性 | 扩充校准集、逐层分析量化误差 |
| 端侧掉帧 | 内存带宽不足 | 降分辨率、降通道数、开 int8 |
7. 一些安利和心得
整个 YOLOv11 + BiFPN + RepVGG 的组合,我自己实践下来最满意的一点是:它不需要你在精度和速度之间做非此即彼的妥协。BiFPN 负责把精度往上提,RepVGG 负责把推理成本往下压,两者恰好形成互补。
如果你手里有一个已经用 v8 跑通的项目,迁移到这套组合大概需要一周时间。前两三天做网络结构改造和训练验证,中间两天做重参数化和导出测试,最后一天做端侧部署和量化调试。比起从零训练一个模型,这个改造成本非常低了。
最后分享一个小技巧:RepVGG 重参数化后建议做一次模型蒸馏。用重参数化前的高精度多分支模型当教师,用重参数化后的单分支模型当学生,蒸馏大概 20 个 epoch,单分支模型的精度还能再涨 0.5 到 1 个点。这个操作成本很低,但收益很稳定。我在多个数据集上都验证过这个规律,你可以直接拿去用。