
之前多数项目都直接放在Linux服务器上跑这次需要把过程挪到一台Win10电脑上训练要用GPU测试却只能走CPU。听起来有点“分裂”但做下来发现这套组合其实很顺手。本文就把这个过程的完整流程、关键配置和踩过的坑整理一遍方便有类似需求的朋友直接参考。先说结论Nanodet这类轻量级目标检测模型天然适合这种“GPU训练、CPU测试”的工作流。训练阶段用GPU加速反向传播把迭代验实验的时间压到最短测试阶段用CPU模拟真实部署环境得出的推理速度才更有参考价值。整个过程不需要太高端的硬件也不需要跨平台迁移在Windows本地就能完整跑通。1. 项目整体设计思路为什么要拆成“CPU测试、GPU训练”1.1 先聊清楚Nanodet是什么Nanodet是一个轻量级的anchor-free目标检测模型核心卖点是模型参数量小、计算量低。同样是检测任务传统检测模型的backbone可能需要几千万甚至上亿参数而Nanodet常用ShuffleNetV2这类轻量backbone整个模型参数量压缩到百万级别。这就带来一个很关键的后果它在CPU上也能跑出可用的速度。锚框方面也省心。传统模型需要手工设计锚框尺寸和比例碰上小目标或者长宽比悬殊的物体还得反复调参。Nanodet直接走anchor-free路线把物体检测变成“中心点边框回归”的问题配置里少了一大块锚框参数。这点在做自定义数据集时非常省事这也是当初没有继续用系列工具的原因之一。1.2 “双模式”工作流的合理性开发过程中训练和测试其实承担着不同的任务。训练阶段大部分时间花在梯度计算和参数更新上这是典型的并行计算场景。GPU动辄几千个核心同时工作一个小数据集在CPU上可能要跑两三个小时在GPU上十几分钟就结束。如果硬要用CPU训练试错成本会高到让人崩溃每调一次学习率都要等半天。测试阶段则完全相反。部署环境通常没有高端GPU甚至就是一台普通办公电脑。模型最终要在这些设备上运行那测出来的速度才有参考价值。如果测试也安排在GPU上跑出来的几十毫秒延迟到了真实部署环境会变成几百毫秒误导性非常强。所以“GPU训练、CPU测试”其实是性价比最高的方案GPU管开发速度CPU管部署真实性。1.3 完整流程串一遍这里直接给出我实际跑通的路径后面所有章节围绕这条线展开在Win10上安装Python环境和PytorchCPU和GPU版本共存或按需切换采集并标注自定义数据集整理成VOC格式把VOC标注转换成Nanodet需要的COCO格式修改配置文件指定类别数、训练参数、设备类型用GPU启动训练监控loss和显存占用训练结束后拿到权重文件用CPU跑推理测试记录性能数据调整线程数等参数做进一步优化这个过程下来整个链路都清晰了后面换数据集换场景也只是替换数据而已。2. Win10环境搭建实操2.1 我的这套Win10环境清单先放硬件参考方便对照项目配置操作系统Windows 10 22H2CPU六核十二线程主频2.6GHz内存16GB DDR4GPUNVIDIA入门级独立显卡6GB显存硬盘512GB SSD这套配置算是入门级显卡型号别太在意重点是6GB显存能跑得动大多数单模型训练任务。Nanodet的显存占用大概4GB左右batch size设保守一点就能稳定跑。软件层面我用了Anaconda管理Python环境版本锁定3.8。这个版本对Pytorch和后续编译工具兼容性最好不需要用更新的3.10或3.11没必要去踩版本兼容的坑。2.2 依赖安装顺序与版本匹配环境搭建最忌讳想到什么装什么顺序错了很容易出现“A依赖BB依赖C最后C和A打架”的情况。我按下面这个顺序装完没有再出过问题conda create -n nanodet python3.8 conda activate nanodet pip install torch torchvision torchaudio --index-url 你的CUDA版本对应地址 pip install opencv-python pyyaml tqdm cython这里最需要留意的是Pytorch和CUDA的匹配关系。NVIDIA驱动会提供一个CUDA运行版本但Pytorch官方是不是支持这个版本要看对应发布说明。实际操作中先查本机显卡驱动支持的CUDA版本再选对应的Pytorch安装命令就不会出现torch.cuda.is_available()返回False的情况。常用的匹配关系可以参考CUDA版本Pytorch支持情况11.1需要安装nightly或早期版本11.3对应Pytorch 1.10以上11.8对应Pytorch 2.x12.1对应Pytorch 2.1以上确认匹配关系后安装Pytorch时优先用官方匹配命令避免用默认的PyPI源装上CPU版。另外Windows上有一个隐形的依赖Visual Studio Build Tools。Nanodet部分C扩展在Windows上需要编译比如pycocotools或者NMS模块没有编译环境会直接报Microsoft Visual C 14.0 is required。这个不是装Visual Studio全家桶装Build Tools工具集就行。2.3 环境自检这样做装完依赖后先跑一段代码确认环境状态import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.device_count()) print(torch.cuda.get_device_name(0))预期输出里torch.cuda.is_available()必须是True否则后面训练时指定GPU会报设备不可用。如果返回False优先检查是不是装了CPU版Pytorch其次检查CUDA版本匹配。3. 准备数据并拆解配置文件3.1 把标注数据整理成VOC格式我用标注工具按VOC格式整理了一个小数据集名称叫模拟项目X样本数量1000张左右覆盖了5个类别。目录结构如下datasets/ VOCdevkit/ VOC2007/ Annotations/ image_001.xml image_002.xml JPEGImages/ image_001.jpg image_002.jpg ImageSets/ Main/ train.txt val.txt每个xml记录了一张图片里所有目标的位置和类别train.txt里按行写图片名如image_001不带扩展名。这个格式是后续转COCO的输入目录必须规范不能随便改。3.2 VOC转COCO的操作与原因Nanodet数据加载内部走的是COCO格式即使你只有VOC标注也得先转换。转换工具在官方仓库的tools/dataset_converters目录下按脚本里的说明填写源路径和目标路径执行。转换后会生成一个annotations目录包含json标注文件和对应的图片文件名。这一步容易踩的一个坑是类别编号对应关系。VOC格式的xml里标签名是字符串转到COCO后变成数字id这个映射顺序必须和后续yaml配置里的类别列表保持一致。转换完成后打开json文件人工抽查一遍确认categories里的类别顺序和配置一致能少走很多弯路。3.3 yaml配置文件的逐项拆解Nanodet通过一个yaml文件管理几乎全部训练配置。这里展示一个简化后的实例save_dir: workspace/nanodet_x model: type: NanoDet backbone: name: ShuffleNetV2 head: num_classes: 5 data: train: dataset: name: XMLDataset image_path: datasets/VOCdevkit/VOC2007/JPEGImages label_path: datasets/VOCdevkit/VOC2007/Annotations test: dataset: name: XMLDataset schedule: epoch: 120 batch_size: 16 lr: 0.02 device: cuda逐项说明一下关键配置save_dir模型输出目录建议一个项目独立目录命名改成项目名不然多个项目共用会乱。head.num_classes自定义数据集的类别总数不包括背景。这个数必须和标注映射一致。data.train.dataset.image_path和label_path训练数据路径注意Windows下写成正斜杠或者双反斜杠单反斜杠会被yaml当成转义符。schedule.epoch训练轮数小数据集100轮左右足够数据集大就适当增加到200。schedule.batch_size显存6GB下16是个比较稳的值8G显存可以开32但也要看图片分辨率。schedule.lr初始学习率batch越大学习率可以适当调高batch 16配0.02比较常见。device训练时填cuda测试时需要改成cpu。4. GPU训练过程与参数监控4.1 训练前的自查清单在启动训练前我习惯花两分钟快速过一遍下面这些点配置文件里的类别数和标注是否一致错一个就全乱套。数据路径下是否有足够的图片建议每个类别至少50张数据太少梯度很容易崩。显卡驱动是否能被Pytorch识别那句torch.cuda.is_available()已经提前跑过了。batch size是否在显存可承受范围内不确定就先开8试跑几个iteration看CUDA memory变化。这些检查看起来琐碎但能在正式跑训练前拦截掉大部分浪费时间的问题。4.2 启动训练的完整过程进入Nanodet工程目录执行python tools/train.py --config config/nanodet_x.yml训练启动后控制台会滚动打印每个iteration的loss值、当前epoch数、学习率、单次迭代耗时等。我第一次跑模拟项目X时loss从刚开始的3.5左右慢慢降到接近0.5整体曲线是下降的趋势。训练过程会用掉约4.2GB显存GPU利用率在80%到95%之间波动训练期间电脑还能正常浏览网页但不建议同时跑其他大负载程序。配置了120个epoch整个训练过程大约55分钟。每个epoch结束都会保存一次权重文件最终目录里会有一个model_final.pth和一个model_best.pth。best是按验证集损失或mAP挑出来的最优权重后面测试阶段默认用这个文件。4.3 训练中loss震荡和学习率策略训练过程中最常见的现象是loss上下波动看起来“不稳”。其实轻微震荡是正常的尤其是batch size较小的场景。但如果loss一直降不下来或者掉到某个值后反弹得很厉害就比较需要注意了。Nanodet默认带warm up机制前几个epoch用一个较小学习率逐步过渡到正式值这能大幅减少起始阶段的震荡。我一般还会在配置里加上cosine衰减让学习率在训练后半段逐步降低这样模型在最后阶段能收敛得更细腻。调优经验模型不收敛时第一反应是把学习率降到原来的三分之一或四分之一而不是盲目加epoch。学习率太高会导致loss在某个值附近反复横跳很难落到更低的位置。5. CPU推理测试把模型从GPU搬下来5.1 测试环境怎么搭最省事最省事的方式是用原来的conda环境直接跑CPU推理因为Pytorch支持CPU和GPU两种模式切换。如果在配置阶段单独装了GPU版PytorchCPU推理时它也会自动退化为CPU模式不需要重新装环境。我在实际测试中特意把环境和工程目录复制了一份到另一台没有NVIDIA显卡的笔记本上用来验证“干净环境”下的部署表现。注意权重文件是用GPU训练出来的加载时加一个map_locationcpu就彻底摆脱GPU依赖了。5.2 CPU推理的几条调优路径CPU推理性能主要受三个因素影响推理线程数Pytorch默认不限制线程数但线程太多反而会在CPU多核之间频繁切换上下文。我实测下来六核机器设置4个线程时性能最好。可以通过torch.set_num_threads(4)设置。输入分辨率这是最直接的性能杠杆。图片从640x640降到320x320推理时间几乎降到三分之一精度损失通常不大。模型是否进入eval模式Pytorch的BN层和dropout在train和eval模式下行为不同忘记调用model.eval()会导致推理结果不稳定、速度变慢。5.3 用官方demo脚本跑一张图官方工程里通常带demo脚本推理命令大致长这样python tools/demo.py --config config/nanodet_x.yml --model workspace/nanodet_x/model_best.pth --image test.jpg --device cpu注意--device cpu这个参数这一步是“CPU测试”的关键。运行完成后会在输出目录生成一张画好框的图片画框越准确说明训练效果越好。如果找不到demo脚本也可以自己写一个最小推理脚本import torch import cv2 import numpy as np from nanodet.util import cfg, load_config from nanodet.model.arch import build_model load_config(cfg, config/nanodet_x.yml) model build_model(cfg.model) weights torch.load(workspace/nanodet_x/model_best.pth, map_locationcpu)[state_dict] model.load_state_dict(weights) model.to(cpu).eval() image cv2.imread(test.jpg) h, w image.shape[:2] # 按配置参数做预处理并转tensor # 模型输出检测框坐标、类别的概率解析后画到图片上这段代码里最容易漏的就是map_locationcpu。不加这个参数在纯CPU机器上会直接报错“找不到GPU设备”加了之后权重就能强制映射到CPU内存。5.4 实测性能记录用模拟项目X的权重在CPU上测了几张不同分辨率的图片结果如下输入分辨率CPU推理耗时帧率参考320x32038ms26 FPS416x41672ms13 FPS640x640168ms5 FPS如果只是验证模型“能不能跑”320x320的分辨率已经足够耗时控制在40毫秒左右。需要更细致的检测边框时再切到416以上。训练完的模型在GPU上推理一张640x640图片大约8msCPU慢了二十倍左右但依然属于可用范围毕竟轻量级模型的设计初衷就是低算力环境部署。6. 常见报错与避坑实录6.1 我实际碰到的几个问题直接罗列我这次实操中遇到的报错和解决过程。第一个是CUDA out of memory。出现原因很简单batch size设了326GB显存装不下。解决方法是把batch size减成16同时关掉数据加载器里不必要的辅助进程。第二个是FileNotFoundError: ROOT does not exist。现象是路径明明存在但程序找不到。原因出在Windows路径上配置里写的是反斜杠路径yaml解析时把\N当成了转义。把所有路径统一改成正斜杠/后就正常了。第三个是ImportError: Microsoft Visual C 14.0 is required。这个在Windows平台很常见安装Visual Studio Build Tools的C桌面开发组件后解决。这个工具集体积比较大但属于一次安装长期受益。6.2 防坑速查表出现环节典型坑解法环境安装Pytorch装了CPU版确认安装命令包含cuda版本标识环境安装CUDA版本和显卡驱动不匹配更新驱动或选匹配的Pytorch数据准备VOC转COCO后类别顺序错乱打开json检查categories映射配置修改路径含中文或空格把项目放到纯英文路径配置修改yaml里单反斜杠被转义路径统一用正斜杠训练GPU显存不足降低batch size训练loss震荡明显调低初始学习率开启warm up推理纯CPU机器加载GPU权重报错加载时加map_locationcpu推理输出框偏移确认是否调用model.eval()6.3 两条独家避坑心得一是训练前先去任务管理器看一眼GPU的利用率和显存。如果NVIDIA显卡利用率是0大概率环境没装对不要盲目开始训练。二是训练过程的日志要保存下来。训练结束后翻日志看loss曲线能客观判断模型是否正常收敛。如果训练中途重启过记得备份上一次的权重文件避免覆盖。整个流程做完我自己最大的体会是开发阶段用GPU跑训练、部署阶段用CPU跑测试看似是在折腾两套环境实际上是把“开发速度”和“部署真实感”都照顾到了。与其纠结一块显卡同时干两件事不如明确分工反而省心。最后再分享一个小技巧如果后续场景需要进一步提高CPU推理速度可以考虑把训练好的权重导出成ONNX格式再借助一些本地的CPU加速库来跑推理。Nanodet官方工程里带导出脚本导出的模型在CPU上还能明显再快一截甚至可以脱离Pytorch环境运行部署时的依赖少了很多。这个方法实测可用值得一试。