ghostnet

9 downloads 0 likes on ModelScope updated

Model card

GhostNet图像分类模型

1. 方案简介

1.1 算法流程介绍

1. 模型背景

GhostNet 是一款轻量级卷积神经网络模型,由 华为诺亚方舟实验室(Noah’s Ark Lab) 于 2020年在论文《GhostNet: More Features from Cheap Operations》中首次提出,并发表在 CVPR 2020 上。该模型旨在为资源受限设备(如移动终端、嵌入式系统、AI芯片等)提供更高效率的神经网络结构,在降低模型计算复杂度和参数规模的同时,尽可能保持较高的分类精度。GhostNet 针对传统卷积神经网络中的“计算冗余”问题提出了一种创新的解决方案:Ghost Module(幽灵模块)。研究者观察到,神经网络中许多特征图之间存在较强的冗余性,因此并不需要使用高成本的标准卷积操作来生成所有特征图。GhostNet 通过少量真实卷积生成部分“原始特征”,然后使用一系列**低成本的线性操作(如深度可分离卷积)**在这些原始特征的基础上“扩展”出更多“Ghost”特征图,从而大大减少计算量。

GhostNet 的主要特点如下:

  • 参数量和计算量大幅减少,特别适合部署在 FPGA、NPU 等定制硬件上;
  • 兼容主流轻量化框架(如 MobileNet、ShuffleNet 等),可作为替代结构无缝集成;
  • 结构设计简单,易于硬件并行优化和流水线加速;
  • 在 ImageNet 等标准数据集上,GhostNet 在同等或更低计算开销下实现了优于同类模型的分类精度。

GhostNet 击败了其他 SOTA 轻量级 CNN,例如 MobileNetV3 和 FBNet。

2. 算法流程图

graph TD
    A[输入图像] --> B[前处理]
    B --> C[Stage1: 标准卷积]
    C --> D[Stage2-4: Ghost模块堆叠]
    D --> E[全局平均池化]
    E --> F[全连接层]
    F --> G[后处理]

3. 前处理(Host端CPU处理)

前处理流程:

graph LR
    A[输入图像路径] --> B[图像加载]
    B --> C[尺寸变换]
    C --> D[张量转换]
    D --> E[DMA准备]

    subgraph 前处理流程
        B -->|cv::IMREAD_COLOR| B1["示例:/data/dog.jpg"]
        C --> C1[Resize短边224px] --> C2["示例:1280x720→256x256"]
        C2 --> C3[rCenterCrop] --> C4["示例:224x224"]
        D --> D1["数据类型转换 uint8→float32"]
        D1 --> D2["归一化 /255.0"]
        D2 --> D3["通道转换 BGR→RGB"]
        D3 --> D4["添加Batch维度 [1,3,224,224]"]
        E --> E1["配置DMA参数"] --> E2["Host→NPU数据传输"]
    end

    style B fill:#F8CECC,stroke:#B85450
    style C fill:#D5E8D4,stroke:#82B366
    style D fill:#DAE8FC,stroke:#6C8EBF
    style E fill:#FFF2CC,stroke:#D6B656

关键操作:

PicPre img(img_path, cv::IMREAD_COLOR); // 图像加载
img.Resize(resize_hw, PicPre::SHORT_SIDE).rCenterCrop(crop_hw); // 调整尺寸+中心裁剪
Tensor img_tensor = CvMat2Tensor(img.dst_img, network); // OpenCV Mat转Tensor
dmaInit(run_sim, netinfo.ImageMake_on, img_tensor, device); // DMA传输初始化

具体处理流程:

  1. 图像加载:通过OpenCV读取原始图像(cv::IMREAD_COLOR保持RGB格式)
    • 示例:输入 img_path 为 "/data/dog.jpg"
  2. 尺寸变换:
    • Resize:将短边缩放到指定尺寸(如256px),保持长宽比
    • rCenterCrop:居中裁剪到模型输入尺寸(如224x224)
    • 示例:原始图像1280x720 → Resize为256x256 → 中心裁剪224x224
  3. 张量转换:
    • CvMat2Tensor 完成:
      • 数据类型转换(uint8 → float32)
      • 归一化(如除以255.0)
      • 通道顺序转换(BGR → RGB,若模型需要)
      • 添加Batch维度(shape变为[1,3,224,224])
  4. DMA准备:
    • dmaInit 配置DMA传输参数,将Tensor数据从Host内存复制到NPU设备内存

4. 后处理(Host端CPU处理)

后处理机制:

graph LR
    A[NPU输出张量] --> B[数据回传]
    B --> C[结果解析]
    C --> D[概率计算]
    D --> E[结果筛选]
    E --> F[可视化处理]

    subgraph 后处理流程
        B --> B1["to(HostDevice::MemRegion())"]
        B1 --> B2["示例:shape=[1,1000]"]
        C --> C1["(float*)host_tensor_class.data().cptr()"]
        D --> D1["softmax转换"] --> D2["示例:[2.1,-1.3,0.8]→[0.85,0.02,0.11]"]
        E --> E1["topK(5)"] --> E2["示例:[(232,0.92),(123,0.07)]"]
        F --> F1["cv::putText绘制"] --> F2["示例:Labrador Retriever : 0.92"]
    end

    style B fill:#F8CECC,stroke:#B85450
    style C fill:#D5E8D4,stroke:#82B366
    style D fill:#DAE8FC,stroke:#6C8EBF
    style E fill:#FFF2CC,stroke:#D6B656
    style F fill:#E1D5E7,stroke:#9673A6

关键操作:

// 数据回传
auto host_tensor_class = output_tensors[0].to(HostDevice::MemRegion());
// 结果解析
auto outputs_class = (float*)host_tensor_class.data().cptr();
// Softmax归一化
auto softmax_class = softmax(outputs_class, 1, 0, N_CLASS); 
// Top-K筛选
auto topk_res = topK(softmax_class, 5);
// 结果可视化
cv::putText(img.ori_img, text.str(), ...);

具体处理流程:

  1. 数据回传:
    • to(HostDevice::MemRegion()) 将NPU输出张量复制回Host内存
    • 示例:输出张量shape为[1,1000],表示ImageNet分类结果
  2. 概率计算:
    • softmax 将原始logits转换为概率分布
    • 示例:输出[2.1, -1.3, 0.8,...] → [0.85, 0.02, 0.11,...]
  3. 结果筛选:
    • topK 提取前5个最高概率的类别
    • 示例:返回 [(232, 0.92), (123, 0.07), ...](类别ID+概率)
  4. 可视化处理:
    • 通过OpenCV将结果绘制到原始图像
    • 示例:在图像左上角添加"Labrador Retriever : 0.92"等文字

1.2 硬件资源分配

1. 硬件映射策略

graph TD
    subgraph CPU后端
        E1[Softmax] --> E2[Top-K筛选]
        E2 --> E3[可视化渲染]
    end
    
    subgraph NPU
        D1[Ghost主卷积] --> D2[深度卷积]
        D2 --> D3[特征融合]
    end
    
    subgraph ImageMake模块
        C1[数据归一化] --> C2[排布转换]
        C2 --> C3[通道对齐]
    end

    subgraph CPU前端
        B1[图像加载] --> B2[动态Resize]
        B2 --> B3[中心裁剪]
    end
    
    A[输入图像] --> B[CPU前处理]
    B --> C[ImageMake硬件加速]
    C --> D[NPU推理]
    D --> E[CPU后处理]

2. 硬件资源分配方案总表

处理阶段 主要操作 硬件分配 代码对应位置 优化措施
1. 前处理部分
- 图像加载 读取图像文件,格式转换 CPU PicPre img(img_path, cv::IMREAD_COLOR)
- 动态Resize 短边缩放(保持长宽比) CPU img.Resize(resize_hw)
- 中心裁剪 裁剪至模型输入尺寸 CPU .rCenterCrop(crop_hw)
2. ImageMake硬件加速
- 数据归一化 y=(x–mean)*scale 代替传统归一化 FPGA dmaInit(..., mean, scale) 硬件定点化运算
- 排布转换 四周Padding(最大255像素) FPGA 自动完成 动态配置Padding参数
- 通道对齐 补通道至4对齐,位宽转换 FPGA AlignAxis硬件替代 自动通道填充策略
3. NPU计算部分
- Ghost主卷积 常规卷积生成部分特征 NPU session.forward()中的Conv2D 核融合(Conv+BN+ReLU6)
- 深度卷积 分组卷积生成剩余特征 NPU session.forward()中的DWConv
- 特征拼接 合并主卷积与廉价操作输出 NPU 编译器自动优化 张量拼接硬件指令加速
4. 后处理优化
- Softmax 将输出转换为概率分布 CPU softmax(outputs_class,...)
- Top-K筛选 提取前5类别 CPU topK(softmax_class,5)
- 可视化渲染 添加分类结果文字标签 CPU cv::putText()

3. 硬件-软件交互示意图

sequenceDiagram
    participant Host as Host CPU
    participant NPU as NPU/PL
    participant DDR as 设备内存

    Host->>Host: 图像加载/Resize/Crop
    Host->>DDR: DMA传输预处理数据
    NPU->>NPU: 执行模型推理
    DDR->>Host: DMA回传输出张量
    Host->>Host: Softmax/TopK处理
    Host->>Host: 结果可视化与保存

2. 模型导出说明

在部署 GhostNet 网络时,整体计算流程可分为两部分:Host 侧(CPU)处理 与 Device 侧(硬件加速)处理。

2.1 Host 侧处理(前后处理)

GhostNet 部署流程中的前处理和后处理通常由 Host CPU 完成,主要包括:

  • 前处理
    • 图像加载:如通过 OpenCV / PIL 等方式读取图像;
    • 动态 Resize:根据模型输入要求进行等比缩放;
    • 中心裁剪:对缩放后的图像进行中心裁剪,保持输入维度一致;
  • 后处理:
    • Softmax:对分类 logits 输出进行归一化;
    • Top-K 筛选:从 softmax 结果中选出概率最高的前 K 类别。

2.2 Device 侧处理(GhostNet 主干)

1)基础卷积算子:

  • 常规卷积:nn.Conv2d直接实现
  • 深度可分离卷积:通过nn.Conv2d的groups参数实现分组卷积
  • 批归一化:nn.BatchNorm2d直接实现
  • 激活函数:
    • ReLU:nn.ReLU或F.relu
    • hard_sigmoid:通过F.relu6(x + 3.) / 6.数学表达式实现

2)Ghost模块特有算子:

  • 主卷积:nn.Conv2d+nn.BatchNorm2d+nn.ReLU组合
  • 廉价操作:分组nn.Conv2d+nn.BatchNorm2d+nn.ReLU组合
  • 特征拼接:torch.cat直接实现

3)注意力机制算子:

  • 全局平均池化:nn.AdaptiveAvgPool2d(1)
  • 通道压缩/扩展:nn.Conv2d实现的1x1卷积
  • 门控函数:hard_sigmoid函数实现

4)特殊操作函数:

  • _make_divisible:通过数学运算int(v + divisor / 2) // divisor * divisor实现
  • hard_sigmoid:通过F.relu6(x + 3.) / 6.实现

5)结构模块:

  • GhostBottleneck:组合多个nn.Conv2d、nn.BatchNorm2d和nn.ReLU
  • ConvBnAct:nn.Conv2d+nn.BatchNorm2d+nn.ReLU顺序组合

GhostNet 与 Icraft 硬件支持算子对应表

GhostNet 功能组件 PyTorch 实现方式 Icraft 对应算子组合
常规卷积 nn.Conv2d Conv2d
分组卷积 nn.Conv2d(groups=...) Conv2d(设置 groups 参数)
深度可分离卷积 nn.Conv2d(groups=in_channels) Conv2d
批归一化 nn.BatchNorm2d BatchNorm2d
ReLU 激活函数 nn.ReLU 或 F.relu ReLU 或 Hardtanh 或 Swish
HardSigmoid F.relu6(x + 3.) / 6. Hardsigmoid
HardSwish x * F.relu6(x + 3.) / 6. Hardswish
Ghost 主卷积模块 Conv2d + BN + ReLU Conv2d + BatchNorm2d + ReLU
Ghost 廉价操作模块 分组Conv2d + BN + ReLU Conv2d + BatchNorm2d + ReLU(groups>1)
特征拼接 torch.cat([x1, x2], dim=1) Concat
AdaptiveAvgPool2d(1) nn.AdaptiveAvgPool2d(1) AvgPool2d(kernel=size=feature map)
1x1卷积(压缩/扩展) nn.Conv2d(kernel_size=1) Conv2d
通道注意力门控 HardSigmoid Hardsigmoid
_make_divisible int(v + divisor / 2) // divisor * divisor 非算子,由 前处理代码逻辑实现
GhostBottleneck 多个 Conv + BN + ReLU + Add + SE 模块组合 Conv2d + BatchNorm2d + ReLU + Concat + Add
ConvBnAct 快速模块 Conv2d + BatchNorm2d + ReLU Conv2d + BatchNorm2d + ReLU
通道间注意力(SE) GAP → Conv1x1 压缩 → ReLU → Conv1x1 扩展 → HardSigmoid AvgPool2d + Conv2d + ReLU + Conv2d + Hardsigmoid

2.3 导出过程和1_scripts中的脚本运行说明

直接加载模型,只导出GhostNet 主干,前后处理不保存在在导出模型中。

├── 0_infer.py       >框架下推断脚本
├── 1_save.py        >trace模型保存onnx/pt脚本
├── 2_save_infer.py  >onnx/pt推断脚本
  • 0_infer.py
    • 可以推理一张图(或其他格式数据)并得到最终结果,目的是为了快速跑通推理代码,为后续剥离/移植前后处理做准备。为了实现快速不能用整个数据集作为输入。
    • 用法: python 0_infer.py
  • 1_save.py
    • 保存模型。
    • 导出的模型默认存放至2_compile/fmodel路径下,如../2_compile/fmodel/ghostnet_224x224.pt
    • 用法: python 1_save.py
  • 2_save_infer.py
    • 用修改后保存的模型做前向推理,目的是为了与0_infer的结果对比,确保导出静态模型的正确性。
    • 用法: python 2_save_infer.py

3. cpu处理部分c++工程移植说明

3.1 cpu环节实现的功能

CPU(Host 端)承担了模型部署与推理流程中大量关键的前处理、后处理和控制逻辑。

1)配置与初始化(控制逻辑)

  1. 读取 YAML 配置文件:
    • 包括模型路径、仿真与实卡运行开关、是否可视化输出、是否保存结果等。
    • 加载图像路径、类别标签文件、网络输入尺寸等信息。
  2. 构建模型路径、打开设备并加载模型:
    • 根据配置动态拼接 JSON 模型路径和 RAW 参数路径。
    • 判断是否在仿真(sim)还是实际设备(实卡)上运行。
    • 加载网络结构并初始化 Session 和 Device。

2)前处理(Preprocess)

这是典型的 在 CPU 上运行的图像前处理操作,目标是将原始图片转为模型输入格式:

  1. 图像读取:
    • 使用 OpenCV 加载图像为 BGR 格式。
  2. 图像缩放与裁剪:
    • 首先根据短边进行等比例缩放(Resize(resize_hw, PicPre::SHORT_SIDE))。
    • 然后执行中心裁剪(rCenterCrop(crop_hw))获得定尺寸输入。
  3. 图像转换为张量:
    • 将裁剪后的图像转为 NCHW 格式、float 类型张量,用于输入模型(CvMat2Tensor())。
  4. 输入 DMA 传输:
    • 将 CPU 中的图像张量传输给设备端执行推理(dmaInit())。

3)模型执行控制

  1. 执行模型前向推理:
    • 由 CPU 调用 session.forward() 启动一次推理流程,控制输入和获取输出。
    • 返回的是在设备端计算完成的结果张量。
  2. 设备端输出转回 Host 内存:
    • 将设备端输出张量(float*)搬运回 CPU 内存空间供后处理使用(to(HostDevice::MemRegion()))。

4)后处理(Postprocess)

这些操作也是由 CPU 进行的,主要包括:

  1. Softmax 计算:
    • 将模型输出转化为类别概率分布。
  2. Top-K 筛选:
    • 从 softmax 输出中选取概率最高的前 5 个类别及其概率。
  3. 可视化与输出处理:
    • 在图像上用 OpenCV putText 写入类别名称和概率。
    • 根据平台(Windows 或 Linux)显示图像、保存图像或保存分类结果为 TXT 文件。

3.2 各环节耗时

命令行输入icraft run ./config/GhostNet_8.toml,以仿真int8量化后的模型为例,单一输入图像对理时间输如下:

// icraft run ./config/GhostNet_8.toml

[05/18/25 13:33:38.384] [I] network wall:   0.0139 ms
Load Json Time:   74.988 ms
Load Raw Time:   60.878 ms
opId: 0    opType: Input           wall:   20.548 ms  mem:    0.000 ms  hard:    0.000 ms  other:   20.548 ms
opId: 1    opType: Resize          wall:    2.022 ms  mem:    0.000 ms  hard:    0.000 ms  other:    2.022 ms
opId: 846  opType: ImageMake       wall:    8.844 ms  mem:    0.000 ms  hard:    0.000 ms  other:    8.844 ms
opId: 1300 opType: HardOp          wall:  801.274 ms  mem:    0.000 ms  hard:    0.000 ms  other:  801.274 ms
opId: 1101 opType: Cast            wall:    0.101 ms  mem:    0.000 ms  hard:    0.000 ms  other:    0.101 ms
opId: 1102 opType: PruneAxis       wall:    0.092 ms  mem:    0.000 ms  hard:    0.000 ms  other:    0.092 ms
opId: 437  opType: Output          wall:    0.012 ms  mem:    0.000 ms  hard:    0.000 ms  other:    0.012 ms

当前仿真系统的cpu配置情况

D:\dtpw\Desktop\icraft_workplace\ghostnet\2_compile>wmic cpu get name,numberofcores,numberoflogicalprocessors,maxclockspeed
MaxClockSpeed  Name                                    NumberOfCores  NumberOfLogicalProcessors
1800           AMD Ryzen 7 4800U with Radeon Graphics  8              16

分析

  • 核心推理耗时在 HardOp,即模型主干部分(801ms),这是真正的前向计算阶段;
  • 前处理部分(Resize + ImageMake)耗时约 10.8ms;
  • 类型转换和输出阶段耗时非常低(<0.2ms);
  • 输入阶段 Input 的 20ms 大概率包括从文件加载图像时间;
  • 虽然 network wall 显示为 0.0139ms,但它不是整个模型推理的时间,而是启动模型调度器的一个时间片段,不代表整体执行。

4. ICRAFT编译

编译通过

  • 使用运行时仿真得到的仿真效果截图或数据,需提供下述表格

    比赛报告中要求有:框架动态图推理、导出静态模型推理、解析组件仿真、量化组件仿真、指令组件仿真的仿真效果截图或数据。对于解析组件仿真、量化组件仿真、指令组件仿真等中间过程仿真该怎么做?文档中并没写明,只能参考[docs/Part 2_2 icraft tools.md · spiger/TUTORIAL - 码云 - 开源中国](https://gitee.com/mxh-spiger/tutorial-runtime/blob/tt3.7.1/docs/Part 2_2 icraft tools.md#34-结果分析)中的icraft run工作简单输出每个组件的仿真。

1. 框架动态图推理

  • 输入图片

  • 框架推理

    • python 0_infer.py

    • 输出结果:

      • D:\dtpw\Desktop\icraft_workplace\ghostnet\1_scripts>python 0_infer.py
        input size = torch.Size([3, 224, 224])
        output size = torch.Size([1000])
        
        --------Classification results----------
        Samoyed: 0.6540         //萨摩耶
        Pomeranian: 0.0577      //博美犬
        Eskimo dog: 0.0179      //爱斯基摩犬
        white wolf: 0.0108      //白狼
        Arctic fox: 0.0107      //北极狐
        

2. 导出静态模型推理

  • 输入图片:同上

  • 导出静态模型推理

    • python 2_save_infer.py

    • D:\dtpw\Desktop\icraft_workplace\ghostnet\1_scripts>python 2_save_infer.py
      input size = torch.Size([3, 224, 224])
      GhostNet模型加载完成
      output size = torch.Size([1000])
      
      --------分类结果--------
      Samoyed: 0.6540
      Pomeranian: 0.0577
      Eskimo dog: 0.0179
      white wolf: 0.0108
      Arctic fox: 0.0107
      

3. 解析组件仿真

  • 命令:

    • # GhostNet_8.toml文件中
      [run]
      log_time = true
      log_io = true
      dump_format = "SFB"
      json = "../3_deploy/modelzoo/GhostNet/imodel/8/GhostNet_parsed.json"  					
      raw = "../3_deploy/modelzoo/GhostNet/imodel/8/GhostNet_parsed.raw"                      
      input = "./qtset/imagenet/ILSVRC2012_val_00000003.JPEG"
      backends = "Host"
      
    • 命令行运行\ghostnet\2_compile>icraft run ./config/GhostNet_8.toml

  • 输出截图

4. 量化组件仿真

  • 命令:

    • # GhostNet_8.toml文件中
      [run]
      log_time = true
      log_io = true
      dump_format = "SFB"
      json = "../3_deploy/modelzoo/GhostNet/imodel/8/GhostNet_quantized.json"  					
      raw = "../3_deploy/modelzoo/GhostNet/imodel/8/GhostNet_quantized.raw"                      
      input = "./qtset/imagenet/ILSVRC2012_val_00000003.JPEG"
      backends = "Host"
      
    • 命令行运行\ghostnet\2_compile>icraft run ./config/GhostNet_8.toml

  • 输出截图

5. 指令组件仿真

  • 命令:

    • # GhostNet_8.toml文件中
      [run]
      log_time = true
      log_io = true
      dump_format = "SFB"
      json = "../3_deploy/modelzoo/GhostNet/imodel/8/GhostNet_BY.json"  					
      raw = "../3_deploy/modelzoo/GhostNet/imodel/8/GhostNet_BY.raw"                      
      input = "./qtset/imagenet/ILSVRC2012_val_00000003.JPEG"
      backends = "Host"
      
    • 命令行运行\ghostnet\2_compile>icraft run ./config/GhostNet_8.toml

  • 输出截图

6. 完整的c++工程仿真输出(c++ runtime)

  • 在3_deploy/modelzoo/GhostNet/build_win目录下运行:

    Release\GhostNet.exe ..\cfg\GhostNet_8.yaml  #注意,windows中一般用\,而/可以用但有些时假会报错
    
    Release\GhostNet.exe ..\cfg\GhostNet_16.yaml
    
    Release\GhostNet.exe ..\cfg\GhostNet_mix.yaml
    
  • 输出截图

    • 分类结果在右上角,用蓝色字体标出,可放大验证。

7. 8bit,16bit,混合精度的精度测试结果

  • 分别运行以下命令,得到8bit,16bit,混合精度模型在imagenet2012val的前5000张图像上的分类结果。
Release\GhostNet.exe ..\cfg\GhostNet_test_8.yaml  #注意,windows中一般用\,而/可以用但有些时假会报错

Release\GhostNet.exe ..\cfg\GhostNet_test_16.yaml

Release\GhostNet.exe ..\cfg\GhostNet_test_mix.yaml

结果分别保存在

3_deploy\modelzoo\GhostNet\io\output\8\val_5000
3_deploy\modelzoo\GhostNet\io\output\16\val_5000
3_deploy\modelzoo\GhostNet\io\output\mix\val_5000
  • 运行精度测试命令Release\GhostNet.exe ..\cfg\GhostNet_test_8.yaml 后,使用metrics测试脚本,根据保存的测试结果计算出精度指标。

    metrics测试脚本使用与配置说明:

    • 精度测试脚本存放于:https://gitee.com/mxh-spiger/benchmark
    • 本模型使用 ./topn/topn_acc.py
    • 输入命令benchmark-master\topn>python .\topn_acc.py D:\dtpw\Desktop\icraft_workplace\ghostnet\3_deploy\modelzoo\GhostNet\io\output\8\val_5000 --gtPath ImageNet2012_val5000.txt
    • resDir字段设置为预测结果存放路径
  • 精度结果

    • GhostNet input shape 精度
      int8 [1, 3, 224, 224] top1: 64.92%;top5: 86.1%
      int16 [1, 3, 224, 224] top1: 68.26%;top5: 88.26%

由于混合精度在编译过程不能通过,负责人回复为组件bug,所以没有混合精度的测试结果。

具体bug见:

https://gitee.com/mxh-spiger/fwb/issues/IC8QZ3

8. 编译过程的一些特殊说明

  • 注意,windows中的命令行中,一般用左,右/可以用但有些时假会报错

  • 编译命令注意事项

    • cd 3_deploy\modelzoo\GhostNet
      mkdir -p build_win
      cd build_win
      
      cmake -G "Visual Studio 17 2022" -A x64 ..    # 不要直接cmake ..
      cmake --build . --config Release
      
    • 不能直接使用cmake ..命令,因为如果系统上多种编译器,且默认cmake使用的编译器不是visualstudio2022会出错,所以使用-G "Visual Studio 17 2022" -A x64命令,让cmake使用visualstudio2022编译器编译。