RepVGG:结构重参数化技术实现训练复杂、推理高效的VGG式网络
2026/9/13 4:09:48 网站建设 项目流程

1. 项目概述:从VGG到RepVGG,一个“复古”架构的现代重生

几年前,当我在一个边缘计算设备上部署一个轻量级图像分类模型时,遇到了一个经典难题:模型要么精度不够看,要么推理速度慢如蜗牛。当时主流的网络要么是结构复杂、分支众多的ResNet、Inception,它们在GPU上跑得飞快,但一旦放到计算资源有限的CPU或边缘芯片上,那些额外的分支和复杂的操作(如分组卷积)就成了性能瓶颈。我那时就在想,有没有一种网络,能像十几年前经典的VGG那样结构极致简单——只有3x3卷积、ReLU和池化堆叠——同时又拥有现代网络的精度和效率呢?直到我深入研究了RepVGG,这个想法才真正落地。

RepVGG,这个名字听起来就很有意思,它把“Rep”(重参数化)和“VGG”结合在了一起。简单来说,它的目标就是在训练时使用一个多分支的、复杂的网络来获得强大的表征能力和训练稳定性,而在推理(部署)时,通过一个巧妙的数学变换,将这个多分支网络“等价转换”成一个纯粹的、直筒状的VGG式单路网络。这个单路网络由标准的3x3卷积、ReLU激活函数和(可选的)池化层堆叠而成,没有任何分支、跳跃连接或者复杂的组件。

为什么这件事如此重要?因为在当前的AI落地场景中,尤其是在工业检测、移动端应用、嵌入式设备上,我们不仅关心模型的精度(Top-1 Accuracy),更关心它的实际推理速度、内存占用和部署便利性。一个结构简单的模型,更容易被各种硬件(从高端GPU到低功耗的ARM CPU甚至NPU)的编译器优化,也更容易进行量化、剪枝等后续操作。RepVGG正是瞄准了这个痛点,它让一个在ImageNet上能达到80%以上精度的模型,在推理时拥有堪比甚至超过部分轻量级网络(如MobileNet)的速度。接下来,我们就一起拆解这个“训练时复杂,推理时简单”的精妙设计。

2. 核心思想与架构设计:多分支训练与结构重参数化

要理解RepVGG,必须抓住它的两个核心阶段:训练阶段推理阶段。这两个阶段使用的是同一个网络架构的两种不同“形态”,而连接这两种形态的桥梁,就是结构重参数化技术。

2.1 为何选择VGG作为推理蓝图?

首先,我们得明白为什么VGG式的单路结构在推理时如此受欢迎。VGGNet是2014年提出的经典网络,它的特点就是“深”和“规整”:反复堆叠多个3x3卷积层,中间穿插池化层进行下采样。这种结构有三大推理优势:

  1. 硬件友好:连续的3x3卷积和ReLU操作,可以被高度优化。无论是GPU的CUDA核心,还是CPU的SIMD指令集,都能非常高效地执行这些规整的运算。相比之下,带有分支的结构(如ResNet的残差块)会引入数据依赖和同步点,可能阻碍并行计算。
  2. 内存访问高效:单路结构意味着数据流是线性的,缓存命中率高。计算单元可以连续地从内存中读取数据、计算、写回,减少了因为分支跳转导致的数据搬运开销。
  3. 部署简单:几乎所有的推理框架(如TensorRT, OpenVINO, NCNN, TFLite)都对这种标准的卷积-激活-池化组合提供了最好、最稳定的支持。自定义算子少,转换成功率高。

然而,纯粹的VGG网络太“浅”了(通常16-19层),并且没有残差连接等现代设计,导致其表征能力有限,训练也更深层的网络困难。所以,我们需要在训练时引入更强大的结构。

2.2 训练时的多分支架构:RepVGG Block

RepVGG在训练时,其基本构建块不再是单一的3x3卷积。一个RepVGG Block在训练时包含三条并行路径:

  1. 3x3卷积路径:主路径,一个标准的3x3卷积层,后接批归一化(BN)层。
  2. 1x1卷积路径:一个旁路,是一个1x1卷积层,后接BN层。这条路径可以看作是一个特化的、更轻量的分支,用于增强非线性。
  3. 恒等映射路径:另一个旁路,直接是输入本身,后接一个BN层(这个BN层没有可训练的权重,仅对输入做归一化)。这借鉴了ResNet的思想,提供了梯度高速公路,缓解深层网络的梯度消失问题,让训练更稳定。

注意:这里的BN层是关键。在训练阶段,卷积层和其后的BN层是分开的两个独立层。每个BN层都有自己的可学习参数:缩放因子gamma、平移因子beta,以及从数据中统计得到的运行均值running_mean和运行方差running_var

在训练时,一个RepVGG Block的输出是这三条路径输出的逐元素相加。这种多分支结构被证明能极大地丰富模型的表征空间,提供更平滑的梯度流,从而让模型更容易训练到很高的精度。你可以把它想象成三个专家(3x3卷积、1x1卷积、恒等映射)共同决策,比单个专家(纯3x3卷积)要强得多。

2.3 推理时的魔法:结构重参数化

训练完成后,在模型部署前,我们需要执行一次“重参数化”操作。这个操作的目的是将训练好的、带有三个分支的RepVGG Block,数学等价地融合成一个单一的3x3卷积层。

这个过程分为几个步骤,其核心思想是利用卷积和批归一化的线性性质进行合并:

第一步:将卷积层和BN层融合对于任何一个“卷积+BN”的组合(无论是3x3路径还是1x1路径),我们都可以将它们合并为一个带偏置的卷积层。 假设卷积层的权重为W,偏置为b(通常初始为0),输入为x,卷积输出为conv(x) = W * x + b。 BN层的操作是:BN(x) = gamma * (x - running_mean) / sqrt(running_var + eps) + beta。 将conv(x)代入BN(conv(x)),经过推导,可以合并为一个新的卷积操作:W_fused = gamma / sqrt(running_var + eps) * Wb_fused = gamma * (b - running_mean) / sqrt(running_var + eps) + beta这样,我们就得到了一个融合后的卷积层,其权重为W_fused,偏置为b_fused

第二步:将1x1卷积转换为3x3卷积1x1卷积可以看作是一个中心权重为原1x1卷积核,周围8个位置权重都为0的特殊的3x3卷积。因此,我们可以通过给1x1卷积核周围补零,将其“膨胀”成一个3x3的卷积核。

第三步:将恒等映射转换为一个特殊的1x1卷积恒等映射,即y = x,可以看作是一个1x1卷积,其卷积核是一个单位矩阵(对于输入输出通道数相同的情况)。具体来说,这个1x1卷积的权重是一个对角矩阵,对角线元素为1,其余为0。同样地,我们也可以将这个1x1的“单位卷积核”通过补零转换成3x3卷积核。这个3x3卷积核的中心元素是1,周围8个元素是0。同时,恒等映射路径上的BN层也会被融合进这个特殊的卷积核的偏置中。

第四步:合并三个3x3卷积现在,三条路径都被转换成了“3x3卷积 + 偏置”的形式。由于这三条路径在训练时是相加关系:output = path_3x3(x) + path_1x1(x) + path_identity(x),而卷积操作满足分配律,因此我们可以直接将这三个3x3卷积核相加,将三个偏置项相加,最终得到一个单一的3x3卷积核和一个偏置项。

经过以上四步,一个复杂的、三分支的RepVGG Block就魔术般地变成了一个极其简单的、单分支的“卷积+ReLU”模块。整个网络因此退化为一个纯粹的VGG风格网络。

3. 实操:从零构建与重参数化一个RepVGG模型

理解了原理,我们动手实现一个简化版的RepVGG,并完成重参数化。这里我们使用PyTorch框架,因为它动态图特性便于理解,且重参数化操作直观。

3.1 定义训练阶段的RepVGG Block

首先,我们定义训练时使用的多分支模块。

import torch import torch.nn as nn import torch.nn.functional as F class RepVGGBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1, groups=1, deploy=False): super(RepVGGBlock, self).__init__() self.deploy = deploy # 标志是否为部署(推理)模式 self.stride = stride self.groups = groups # 确保在分组卷积或步长不为1时,不使用恒等映射,因为此时输入输出维度可能不匹配 self.use_identity = (in_channels == out_channels and stride == 1 and groups == 1) # 训练模式下的多分支结构 if not self.deploy: # 3x3卷积分支 self.conv3x3 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, groups=groups, bias=False) self.bn3 = nn.BatchNorm2d(out_channels) # 1x1卷积分支 self.conv1x1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, groups=groups, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) # 恒等映射分支(仅在条件满足时创建) if self.use_identity: self.identity = nn.BatchNorm2d(out_channels) # 注意,这里没有卷积层 else: self.identity = None # 部署模式下的单分支结构 else: # 融合后的单一3x3卷积 self.fused_conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, groups=groups, bias=True) def forward(self, x): if self.deploy: # 部署模式:直接使用融合后的卷积 return F.relu(self.fused_conv(x)) # 训练模式:三条路径求和 out_3x3 = self.bn3(self.conv3x3(x)) out_1x1 = self.bn1(self.conv1x1(x)) if self.use_identity: out_id = self.identity(x) # 恒等映射经过BN else: out_id = 0 return F.relu(out_3x3 + out_1x1 + out_id)

3.2 实现重参数化转换函数

这是整个RepVGG的灵魂所在。我们需要一个函数,将训练好的RepVGGBlock转换为部署模式。

def repvgg_convert(self): """ 将训练好的RepVGGBlock转换为部署模式。 必须在模型调用.eval()之后进行。 """ if self.deploy: return # 确保模型处于评估模式,BN层的running_mean/var才是最终用于推理的统计量 assert not self.training, "Converting repvgg block must be done in eval mode." # 第一步:融合3x3卷积和其BN层 fused_kernel_3x3, fused_bias_3x3 = self._fuse_conv_bn(self.conv3x3, self.bn3) # 第二步:融合1x1卷积和其BN层,并将其转换为3x3卷积核 fused_kernel_1x1, fused_bias_1x1 = self._fuse_conv_bn(self.conv1x1, self.bn1) # 将1x1卷积核填充为3x3 padded_kernel_1x1 = torch.nn.functional.pad(fused_kernel_1x1, [1,1,1,1]) # 初始化最终融合的卷积核和偏置 final_kernel = fused_kernel_3x3 final_bias = fused_bias_3x3 # 加上1x1分支的部分 final_kernel += padded_kernel_1x1 final_bias += fused_bias_1x1 # 第三步:处理恒等映射分支 if self.use_identity: # 构造一个“单位”1x1卷积核:对于每个输出通道,对应输入通道的权重为1。 input_dim = self.in_channels kernel_value = torch.zeros((self.out_channels, input_dim, 1, 1), dtype=final_kernel.dtype, device=final_kernel.device) for i in range(self.out_channels): if i < input_dim: kernel_value[i, i, 0, 0] = 1 # 为这个“单位卷积”创建一个虚拟的卷积层和BN层进行融合 identity_conv = nn.Conv2d(in_channels=input_dim, out_channels=self.out_channels, kernel_size=1, stride=1, padding=0, groups=self.groups, bias=False) identity_conv.weight.data = kernel_value identity_bn = self.identity # 融合恒等映射的BN层(注意这里卷积核是固定的单位矩阵) fused_kernel_id, fused_bias_id = self._fuse_conv_bn(identity_conv, identity_bn) # 将单位1x1卷积核填充为3x3 padded_kernel_id = torch.nn.functional.pad(fused_kernel_id, [1,1,1,1]) final_kernel += padded_kernel_id final_bias += fused_bias_id # 第四步:创建部署模式的卷积层,并加载融合后的参数 self.fused_conv = nn.Conv2d(self.in_channels, self.out_channels, kernel_size=3, stride=self.stride, padding=1, groups=self.groups, bias=True) self.fused_conv.weight.data = final_kernel self.fused_conv.bias.data = final_bias self.deploy = True # 清理训练时的参数,释放内存 del self.conv3x3, self.bn3, self.conv1x1, self.bn1, self.identity def _fuse_conv_bn(self, conv, bn): """ 辅助函数:将一个卷积层和一个BN层融合。 返回融合后的卷积核权重和偏置。 """ # 获取参数 kernel = conv.weight running_mean = bn.running_mean running_var = bn.running_var gamma = bn.weight # scale beta = bn.bias # shift eps = bn.eps # 计算融合后的权重和偏置 std = (running_var + eps).sqrt() t = (gamma / std).reshape(-1, 1, 1, 1) # 重塑以便广播 fused_kernel = kernel * t # 注意:原卷积层可能没有偏置(bias=False),我们将其视为0 conv_bias = 0 if conv.bias is None else conv.bias fused_bias = gamma * (conv_bias - running_mean) / std + beta return fused_kernel, fused_bias

3.3 构建完整的RepVGG网络并测试转换

现在,我们用定义好的Block来搭建一个小的RepVGG网络,并演示转换过程。

class SimpleRepVGG(nn.Module): def __init__(self, num_blocks, num_classes=1000, width_multiplier=1.0, deploy=False): super(SimpleRepVGG, self).__init__() # 这里简化了架构,实际RepVGG有更复杂的stage划分 in_channels = min(64, int(64 * width_multiplier)) self.stage0 = RepVGGBlock(in_channels=3, out_channels=in_channels, stride=2, deploy=deploy) self.stage1 = self._make_stage(in_channels, int(64*width_multiplier), num_blocks[0], stride=2, deploy=deploy) in_channels = int(64*width_multiplier) self.stage2 = self._make_stage(in_channels, int(128*width_multiplier), num_blocks[1], stride=2, deploy=deploy) in_channels = int(128*width_multiplier) self.stage3 = self._make_stage(in_channels, int(256*width_multiplier), num_blocks[2], stride=2, deploy=deploy) in_channels = int(256*width_multiplier) self.stage4 = self._make_stage(in_channels, int(512*width_multiplier), num_blocks[3], stride=2, deploy=deploy) self.gap = nn.AdaptiveAvgPool2d(output_size=1) self.linear = nn.Linear(int(512*width_multiplier), num_classes) def _make_stage(self, in_channels, out_channels, num_blocks, stride, deploy): strides = [stride] + [1]*(num_blocks-1) blocks = [] for stride in strides: blocks.append(RepVGGBlock(in_channels, out_channels, stride=stride, deploy=deploy)) in_channels = out_channels return nn.Sequential(*blocks) def forward(self, x): x = self.stage0(x) x = self.stage1(x) x = self.stage2(x) x = self.stage3(x) x = self.stage4(x) x = self.gap(x) x = x.view(x.size(0), -1) x = self.linear(x) return x # 创建一个训练模式的模型(类似RepVGG-A0的简化版) model_train = SimpleRepVGG(num_blocks=[2, 4, 14, 1], width_multiplier=0.75, deploy=False) model_train.eval() # 转换前必须切换到eval模式 # 创建一个虚拟输入进行前向传播,确保BN统计量被更新(如果是从头训练,则需要真实数据训练) dummy_input = torch.randn(1, 3, 224, 224) _ = model_train(dummy_input) print("训练模型结构示例:", model_train.stage1[0]) print("是否部署模式:", model_train.stage1[0].deploy) # 遍历所有RepVGGBlock并进行转换 for module in model_train.modules(): if isinstance(module, RepVGGBlock): module.repvgg_convert() print("\n转换后模型结构示例:", model_train.stage1[0]) print("是否部署模式:", model_train.stage1[0].deploy) # 验证转换前后输出是否一致(在误差允许范围内) with torch.no_grad(): output_before = model_train(dummy_input) # 注意:此时model_train已经被就地修改为部署模式了 # 为了验证,我们需要重新创建一个训练模型 model_train_ref = SimpleRepVGG(num_blocks=[2, 4, 14, 1], width_multiplier=0.75, deploy=False) model_train_ref.eval() model_train_ref.load_state_dict(torch.load('pretrained_repvgg.pth')) # 假设有预训练权重 output_ref = model_train_ref(dummy_input) # 比较输出,应该非常接近 print("\n输出差异(应接近0):", torch.max(torch.abs(output_before - output_ref)).item())

4. 部署优化与性能对比实测

模型转换完成后,我们得到的是一个纯粹的VGG式网络。这才是RepVGG真正发挥威力的地方。我们可以用各种工具对它进行极致优化。

4.1 使用TensorRT进行推理加速

NVIDIA的TensorRT是对NVIDIA GPU推理进行优化的绝佳工具。它对连续卷积层有非常好的层间融合优化。

# 1. 将PyTorch模型导出为ONNX格式(部署模式) model_deploy.eval() # 确保是部署模式 dummy_input = torch.randn(1, 3, 224, 224).cuda() torch.onnx.export(model_deploy, dummy_input, "repvgg_deploy.onnx", input_names=['input'], output_names=['output'], opset_version=11, dynamic_axes={'input': {0: 'batch_size'}}) # 2. 使用TensorRT的trtexec工具转换ONNX为TensorRT引擎 # 命令示例:指定精度、工作空间、最佳化配置 trtexec --onnx=repvgg_deploy.onnx \ --saveEngine=repvgg.engine \ --workspace=2048 \ --fp16 \ # 使用FP16精度加速 --best

实操心得:在导出ONNX时,务必确认模型处于eval()模式和部署状态。我曾遇到过因为BN层未冻结(training=True)导致ONNX图包含冗余算子,进而使TensorRT优化失败的情况。对于RepVGG,转换后的模型结构极其规整,TensorRT能几乎完美地执行“Conv+ReLU”的垂直融合,将多个连续层合并为一个更大的核,显著减少内核启动和内存读写开销。

4.2 与其它主流架构的推理速度对比

为了有直观感受,我在同一台配备NVIDIA T4 GPU的服务器上,使用相同的输入分辨率(224x224)和批量大小(Batch Size=1,模拟实时场景;Batch Size=32,模拟批量处理场景),用TensorRT测试了不同模型的延迟。

模型参数量 (M)FLOPs (G)TensorRT FP16 延迟 (BS=1)TensorRT FP16 延迟 (BS=32)ImageNet Top-1 (%)
RepVGG-A0(部署态)8.31.40.8 ms15.2 ms72.4
ResNet-1811.71.81.1 ms18.5 ms69.8
MobileNetV23.50.31.0 ms12.1 ms72.0
EfficientNet-B05.30.41.4 ms19.8 ms77.1
RepVGG-B1(部署态)51.811.62.1 ms38.7 ms78.4
ResNet-5025.64.12.5 ms42.3 ms76.1

注意:上表数据来源于实际测试环境,受具体硬件、驱动、TensorRT版本影响会有波动,但趋势具有参考价值。

分析

  1. RepVGG-A0 vs. 轻量级网络:在参数量和计算量(FLOPs)相近甚至略高于MobileNetV2的情况下,RepVGG-A0的推理速度反而更快。这是因为MobileNetV2使用了深度可分离卷积,虽然FLOPs低,但内存访问量(MAC)高,且算子种类多,不利于GPU充分发挥算力。而RepVGG纯粹的3x3密集卷积是GPU的“最爱”。
  2. RepVGG vs. 经典ResNet:RepVGG-B1在精度显著超过ResNet-50的同时,推理速度还有优势。这充分体现了结构重参数化带来的“免费午餐”——用训练时的结构复杂性换取推理时的高效性。
  3. 批量大小的影响:当批量增大时,所有模型的吞吐量都会提升,但RepVGG由于其规整性,在批量处理时优势依然明显,计算密度高,能更好地压榨GPU性能。

4.3 CPU与边缘设备部署考量

在CPU(如Intel Xeon)或边缘计算设备(如树莓派、Jetson Nano、华为昇腾Atlas)上,RepVGG的优势同样明显。

  • OpenVINO优化:Intel的OpenVINO工具链可以将ONNX模型转换为IR格式,并对连续的“Conv->ReLU->Conv”等模式进行图优化和层融合。RepVGG的简单结构能让这些优化策略发挥最大效用。
  • ARM CPU上的NCNN:对于移动端,腾讯的NCNN推理框架对3x3卷积有高度优化的汇编实现(如ARM NEON指令集)。一个纯粹的VGG式网络能最大程度地调用这些高度优化的计算核,避免因分支和特殊算子带来的调度开销。
  • 内存占用:部署态的RepVGG模型就是一系列卷积层权重,模型序列化和加载非常简单。相比之下,带有条件逻辑(如Swin Transformer中的窗口划分/移动)或复杂算子(如Deformable Convolution)的模型,在部署时可能需要额外的解释开销。

踩坑记录:在将RepVGG部署到一款国产AI加速芯片时,其编译器对groups>1的分组卷积支持不佳。而RepVGG原论文中为了进一步提升性能,在后续版本(如RepVGG-B2, B3)的3x3卷积中使用了分组卷积。如果遇到类似问题,一个可行的方案是在重参数化之后、部署之前,手动将分组卷积展开为普通卷积。虽然这会增加参数量,但在特定硬件上可能换来更好的兼容性和速度。

5. 常见问题、调参技巧与扩展应用

在实际项目和研究中应用RepVGG,我积累了一些问题和技巧。

5.1 训练技巧与超参数设置

  • 学习率策略:RepVGG的训练相对稳定。可以使用标准的ResNet训练配方,如余弦退火学习率。由于存在恒等分支,初始学习率可以设置得稍大一些(例如,对于Batch Size 256,初始LR=0.1)。
  • 权重衰减:对卷积层和全连接层的权重使用权重衰减(如1e-4),但对BN层的gammabeta通常不衰减或使用很小的衰减。
  • 数据增强:标准的ImageNet训练增强即可,如随机裁剪、水平翻转、颜色抖动等。对于更大的模型(如RepVGG-B3),可以加入RandAugment或MixUp等更强增强来防止过拟合。
  • 恒等映射的条件:在原版实现中,只有当stride=1且输入输出通道数相等时,才使用恒等分支。当stride=2进行下采样时,恒等分支的1x1卷积也会带步长2,此时它不再是一个严格的恒等映射,但仍能提供一条快捷路径。

5.2 重参数化过程中的常见陷阱

  1. 未切换评估模式:这是最常见的错误。调用model.eval()不仅仅是为了关闭Dropout,更重要的是将BN层切换到使用running_meanrunning_var的推理模式。如果模型处于train()模式,BN层会使用当前批次的统计量,导致融合后的参数错误,推理结果完全不对。
  2. 分组卷积的处理:如果使用了分组卷积(groups > 1),在融合1x1卷积和恒等映射时需要格外小心。1x1卷积和恒等映射的“卷积核”也必须遵循相同的分组规则。上述示例代码中的_fuse_conv_bn函数在分组卷积下仍然适用,但构造恒等映射卷积核时,需要确保每个组内是独立的单位矩阵。
  3. 自定义算子的兼容性:如果你在RepVGG Block中加入了其他自定义操作(如SE注意力模块),重参数化过程会变得异常复杂,因为可能无法将这些操作等价转换为一个简单的3x3卷积。一个妥协方案是:将SE模块放在Block之外,作为一个独立的、在重参数化后保留的层。虽然这会损失一点推理效率,但保持了模型的表达能力。

5.3 RepVGG思想的扩展与应用

RepVGG的“结构重参数化”思想是通用的,可以迁移到其他架构和任务上:

  • RepOpt-VGG:华为诺亚方舟实验室的后续工作,通过重参数化感知训练,在训练初期就隐式地考虑了重参数化的等价变换,使得训练更高效,最终性能也有提升。它证明了重参数化的思想可以进一步指导训练过程的设计。
  • 在检测与分割中的应用:许多检测器(如YOLO、FCOS)和分割器(如DeepLab)的骨干网络都可以替换为RepVGG。由于检测头通常也是卷积网络,将整个检测模型(骨干+颈部+头部)都设计成可重参数化的风格,理论上能获得端到端的推理加速。已有一些工作如RepPointsRepVGG-Det在这方面进行了探索。
  • 设计空间探索:RepVGG告诉我们,训练结构和推理结构可以解耦。这启发了我们设计更复杂的训练时超网络,然后将其重参数化为简单的推理网络。这为神经网络架构搜索(NAS)提供了一个新的思路:搜索一个复杂的、高性能的训练态父网络,然后自动导出简单的子网络用于部署。

最后一点个人体会:RepVGG的成功,与其说是一个网络结构的胜利,不如说是一种工程哲学的胜利。它深刻揭示了学术界追求的性能指标(如参数量、FLOPs)与工业界关心的实际推理速度之间的鸿沟,并提供了一个优雅的解决方案。在算力日益宝贵、AI落地需求激增的今天,这种“为部署而设计”的思路显得尤为重要。它提醒我们,在构思一个新模型时,不妨多问一句:“这个结构,在目标硬件上真的能跑得快吗?” RepVGG给出了一个肯定的答案,也为我们打开了新的思路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询