“模型在本地能跑,换到云服务器就各种崩”,这大概是做目标检测部署时最常听见的一句话。我前段时间刚好把一套YOLOv5模型完整迁移到某云平台的GPU实例上,踩遍了环境编译、权重传输、显存溢出这些坑。这篇内容把整个部署过程拆开讲清楚:先聊实例选型,再给环境搭建的完整命令,然后是模型导出方式对比,最后是上线后怎么保证服务稳定。无论你是刚把模型训练完,还是被服务器上的一堆依赖搞到头疼,这篇都能直接照着操作。
部署这件事,最忌讳的是照着别人的博客一步步敲命令,敲完了不知道为什么。我尽量把每个步骤背后的原因也讲清楚,这样碰到意外情况时你不会慌。
1. 训练环境与部署环境的真实差距:先想清楚再下单
1.1 本地能训不代表能部署
不少同学的习惯是:训练在自己的GPU工作站上跑,跑完拿到一个 .pt 权重文件,就以为万事大吉。实际推上线时发现,服务器上没有显卡驱动、没有CUDA、没有PyTorch,甚至连OpenCV都报错,模型根本加载不起来。
训练和部署本来就是两个场景。训练时需要保存梯度、做反向传播,显存占用大;部署时只需要前向推理,显存占用小很多。但部署环境通常更“干净”,很多训练机上装好的库在这里都不存在。更麻烦的是,服务器如果只有CPU,推理速度会从显卡上的几十毫秒直接变成一两秒,这个差距不是代码能弥补的。
所以在下单云服务器之前,先搞清楚三件事:模型本身的参数量有多大、单次推理耗时要求是多少、同时访问的并发量大概是多少。这三件事直接决定你要买什么样的机器。
1.2 根据模型大小和并发量反推显存
以常用的YOLOv5s为例,默认输入尺寸640x640的图片,批量大小为1时,显存占用大约在1.5GB到2GB之间。如果开半精度推理,还能再降一些。但如果是YOLOv5m、YOLOv5l甚至x系列,显存占用会明显翻倍。我整理了一个估算参考表:
| 模型规模 | 单张推理显存占用(640尺寸) | 建议显存规格 | 适用场景 |
|---|---|---|---|
| YOLOv5s | 约1.5~2GB | 6GB以上 | 轻量线上服务 |
| YOLOv5m | 约3~4GB | 8GB以上 | 精度优先的内部项目 |
| YOLOv5l | 约5~6GB | 12GB以上 | 复杂场景检测 |
| YOLOv5x | 约8GB以上 | 16GB以上 | 离线批量处理 |
如果你希望同时处理多个并发请求,比如一次来10个请求,不要天真地以为显存是“请求数 × 单张显存”。很多实现里每个请求会各自加载一份模型,这会导致显存飙升。更合理的做法是用队列把请求攒起来,用单份模型批量推理,后面第4章我会详细写。
1.3 带宽和机型的选择思路
很多人在选实例时只顾着看GPU型号,忽略公网带宽。如果业务方需要传图片到服务器,带宽太小会导致请求还没到模型就被堵死了。一般图片压缩后在几百KB左右,5Mbps的公网带宽基本够用。如果图片特别大,或者调用方在同一个云内网,建议直接内网访问,不要买公网IP,既安全又快。
这里还有一个小经验:选择GPU实例时,优先看显存,其次看显卡型号。对于YOLOv5s这类轻量模型,主流的中端卡已经能跑得很顺畅,不必盲目追求顶配。云平台一般会提供按量付费的选项,前期调试用按量付费,跑通了再转包年包月,能省不少钱。
2. 从零到一键运行的完整环境搭建:驱动、容器与依赖
2.1 CUDA版本与显卡驱动的匹配原则
服务器的环境配置,第一步往往是安装NVIDIA驱动。这里有个典型误区:直接安装最新版CUDA,结果系统提示驱动版本过旧,两个版本对不上,PyTorch根本用不了GPU。
我建议的顺序是:先装驱动,再用nvidia-smi查看驱动支持的最高CUDA版本,然后选择不高于这个版本的CUDA工具包。比如驱动支持CUDA 12.2,那你用CUDA 11.7或者12.1都没问题。不要装最新版CUDA,因为PyTorch和YOLOv5代码不一定会及时适配新版本。
一套我验证过比较稳妥的组合:NVIDIA驱动版本535+,CUDA 11.7,cuDNN 8.5,PyTorch 1.13以上、2.x也可以。这套组合对YOLOv5s的部署非常友好,网上能找到的绝大多数教程也都基于这个组合,遇到问题更容易排查。
2.2 用Docker容器绕过大量编译问题
坦白讲,安装驱动和CUDA是最容易出问题的环节。如果你不想在这一步浪费太多时间,直接把Docker用起来。Docker容器的好处是:镜像里已经把CUDA、cuDNN、Python环境都装好了,你只需要拉一个带GPU支持的PyTorch镜像,把项目目录挂载进去就能跑。
我之前踩过不少坑之后,现在偏向直接用镜像。一条命令大概是这样:
docker run -d \ --name yolov5-server \ --gpus all \ -p 8000:8000 \ -v /opt/yolov5:/workspace/yolov5 \ -v /opt/models:/workspace/models \ pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime简单解释一下这几个参数:
--gpus all:让容器内能看到宿主机上的GPU,不加的话即使容器里装了CUDA,GPU也不可用。-p 8000:8000:把容器内的8000端口映射到宿主机,方便后面跑HTTP服务。-v /opt/yolov5:/workspace/yolov5:把宿主机上的项目目录挂载进容器,不用把代码打进镜像里,改代码也不需要重建镜像。
如果你不想用Docker,直接在系统里配conda环境也可以,只是每台新机器都要重复一遍安装动作。容器模式下,镜像拉一次,后面在别的机器上直接一跑就完事。
2.3 依赖清单里最容易漏的那几个库
在conda环境里,我给YOLOv5项目准备的依赖大概是这样:
conda create -n yolov5 python=3.9 -y conda activate yolov5 pip install torch==2.0.0 torchvision==0.15.0 --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt重点说下requirements.txt。这个文件里除了PyTorch,还有opencv-python、numpy、matplotlib、pandas等。有两点必须注意:
第一,OpenCV建议直接换opencv-python-headless。云服务器没有显示器,完整版OpenCV会去加载GUI相关的库,没有安装时会出现libGL.so.1报错。headless版本专门为无界面服务器设计,能省掉一大堆系统依赖。
第二,numpy版本不要乱升级。YOLOv5代码里有些操作对numpy版本敏感,我遇到过因为numpy 1.19升级到1.24,导致部分函数行为变化,预处理结果异常。稳妥做法是让requirements.txt里的版本保持官方默认,不要手动追求新版本。
依赖装完后,先用自带的测试图片跑一遍:
python detect.py --source data/images/bus.jpg --weights yolov5s.pt --device 0如果不报错,且runs/detect目录下生成了标注图片,说明环境已经通了。这时候再接入你自己的业务代码,会顺手很多。
2.4 两个高频报错的定位过程
第一个是ImportError: libGL.so.1: cannot open shared object file。这个报错几乎全是OpenCV引起的,解决方法就是前面说的,卸载完整版OpenCV,改装headless版本:
pip uninstall opencv-python -y pip install opencv-python-headless第二个是AssertionError: Torch not compiled with CUDA enabled。这个报错通常是因为PyTorch被装成了CPU版本,也就是安装时没有指定GPU对应的index-url。解决办法也简单:卸载后重装GPU版,装完用下面这行命令验证:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出True和显卡型号,说明GPU已经可用。若输出False,优先怀疑PyTorch版本装错、CUDA驱动不匹配这两类原因。
3. 权重文件、目录结构与模型格式选择
3.1 权重文件从哪里来,标签文件千万别忘了带
部署时拿到的权重文件一般有三种来源:官方预训练权重、自己训练好的权重、第三方分享的权重。
官方预训练权重最简单,适用的是COCO数据集里的80类,比如人、车、猫等。自己训练的权重则对应你自己的类别,更符合业务场景。如果是在自己数据上微调出来的模型,部署时一定要把训练时的data.yaml一并带上。这个文件里定义了类别名,YOLO加载模型时需要从中读取标签列表,漏了它,即使检测框画出来了,输出的类别名称也可能是乱码。
第三方分享的权重相对危险,因为不同版本的YOLOv5对权重的兼容性不一样,我建议拿到权重后第一件事就是检查model.yaml里的模型结构和类别数是否和你环境匹配,而不是直接加载。
3.2 一个能少走弯路的目录组织方式
很多部署事故并不是代码写错,而是文件放乱了。路径硬编码问题在服务器上尤其常见——本地目录和服务器目录不一致,模型加载失败。我自己习惯用这样的目录结构:
/opt/yolov5/ ├── detect.py ├── models/ ├── weights/ │ ├── yolov5s.pt │ └── my_custom.pt ├── data/ │ ├── images/ │ └── labels/ ├── logs/ └── runs/其中weights专门放模型文件,runs放检测输出,logs放服务日志。项目代码里统一用相对路径或环境变量引用,不要写死/home/你的用户名/...这种绝对路径。这样换机器、换用户、换目录,代码都不用改。
3.3 PyTorch、TorchScript、ONNX、TensorRT到底用哪个
部署前经常要在不同模型格式之间做选择。我画了一张对比表,方便你根据自己的情况选:
| 格式 | 推理速度 | 环境依赖 | 部署难度 | 适合场景 |
|---|---|---|---|---|
| PyTorch .pt | 中等 | 需要完整PyTorch环境 | 低 | 快速上线、内部服务 |
| TorchScript | 中等 | 需要Torch库 | 低 | 跨语言调用时的轻量方案 |
| ONNX | 较快 | ONNX Runtime | 中 | 需要跨平台或边缘设备 |
| TensorRT | 最快 | TensorRT引擎 | 高 | GPU服务器上的生产环境 |
如果是第一次在云服务器上部署,我的建议很直接:先用PyTorch的.pt格式把整条链路跑通,不要一上来就碰TensorRT。TensorRT虽然可以把速度再提升一截,但它需要针对显卡型号手动编译engine文件,版本匹配问题非常多,初学时容易在这里耗上一整天。
等PyTorch版本稳定运行几天,再考虑要不要转ONNX或TensorRT做性能优化。
3.4 模型上传服务器的三种方式
模型文件不大,几MB到几百MB,上传方式主要看网络环境。
最简单的是scp:
scp ./weights/my_custom.pt root@你的服务器IP:/opt/yolov5/weights/如果模型文件在100MB以上,建议先压缩再传。.pt文件本身就是Tensor序列化格式,压缩率还可以,压缩后传输时间能减少不少。
如果是多台服务器共用同一份权重,更推荐先传到对象存储服务再下载到各节点,这样后面更新权重时不用一台一台传。我最早图省事,每次更新都scp一遍,后来三台服务器同步时是真的很痛苦。
4. 从“能跑”到“跑得快”:推理性能实测与调优
4.1 第一次推理前的自检清单
环境搭好、模型传上去之后,不要急着启动HTTP服务,先做一轮自检。我总结成三条命令:
nvidia-smi这条看驱动是否正常、显存有没有被其他进程占满。
python -c "import torch; print(torch.cuda.is_available())"这条看PyTorch是否真的识别到了GPU。
ss -lntp | grep 8000这条看端口占用情况,避免服务启动时发现端口被别人占了。
这三步都通过,才算是真正准备好了。我见过不少人跳过自检直接跑服务,最后遇到GPU不可用的问题,排查了半天才发现是环境没配好。
4.2 不同参数下的实测数据
我在一台中等规格的GPU实例上,用YOLOv5s权重跑了几个对比,数据大致如下:
| 推理方式 | 输入尺寸 | Batch大小 | 平均耗时/张 |
|---|---|---|---|
| CPU | 640 | 1 | 约1100ms |
| GPU(FP32) | 640 | 1 | 约22ms |
| GPU(FP16) | 640 | 1 | 约14ms |
| GPU(FP16) | 640 | 8 | 约9ms/张 |
这里要解释一下,最后一个batch=8是把8张图打包成一批,整体推理一次,单张平均耗时能降低。但代价是显存占用更高,同时单次请求的延迟会增加一点。所以做在线服务时,batch不要开太大,通常4到8是个比较平衡的值。
如果你的GPU型号更强,数据会更漂亮,但这个量级可以给你一个参考方向:能上GPU就上GPU,CPU只有实在没得选时才用。
4.3 把半精度推理打开
YOLO部署中最划算的优化,就是把模型精度从FP32降到FP16。半精度推理本质上是用更少的显存存同样的参数,计算速度更快,而检测精度损失通常非常小。YOLOv5的detect.py里其实已经内置了这个开关,在推理时加上--half即可:
python detect.py --source ./test.png --weights yolov5s.pt --device 0 --half如果改用ONNX Runtime,可以在配置里把enable_fp16选项打开。TensorRT部署时同样建议启用FP16模式。不过要记得,FP16推理需要显卡支持,主流的GTX 10系以上显卡基本都能跑,个别老卡或AMD卡可能不支持。
4.4 显存溢出(OOM)不一定是你显卡不好
部署时最常见的报错是CUDA out of memory。很多人第一反应是换大显存的卡,其实先要排查其他原因。
常见原因有:批量大小设置过大、输入图片尺寸超过模型预期、后台还挂着训练任务、或者同时多个进程各自加载了模型。排查顺序我建议是:
- 把batch_size降为1,再看显存占用;
- 把输入尺寸从640降到512,再看显存占用;
- 用
nvidia-smi查看是否有其他进程占着显存; - 检查代码里是否每个请求都加载了一次模型。
第四点尤其容易忽略。如果每次请求都实例化一个模型,显存当然不够用。正确做法是模型只在服务启动时加载一次,推理时反复调用,这个后面讲HTTP服务时会再提到。
4.5 多并发请求的排队策略
把模型写成一个常驻服务后,很容易遇到高并发:业务方一次性传来20张图片,如果同时开20个线程去推理,显存马上爆掉。
正确思路不是提升显存,而是加一个队列,把请求串行化,凑够一个小批量再统一推理。简化逻辑是这样:
queue.put(request) while True: batch = queue.get_batch(size=4) results = model(batch) for result in results: queue.response.put(result)这种方法尤其适合YOLOv5这种本身对单张图片推理延迟敏感的场景。牺牲几个毫秒的排队等待,换来的稳定性和低显存占用,非常值得。
5. 服务上线后的稳定运行经验
5.1 用systemd管住后台进程
很多人在服务器上直接跑类似python app.py的命令,终端一关,服务就没了。哪怕用nohup也有隐患,进程崩溃了不会自动拉起来。
我推荐用systemd管理推理服务。写一个service文件即可:
[Unit] Description=YOLO detection service After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/yolov5 ExecStart=/opt/conda/envs/yolov5/bin/python /opt/yolov5/app.py Restart=always RestartSec=5 Environment=PYTHONUNBUFFERED=1 [Install] WantedBy=multi-user.target保存到/etc/systemd/system/yolov5.service后:
systemctl daemon-reload systemctl enable yolov5.service systemctl start yolov5.service systemctl status yolov5.service其中Restart=always的意思是进程异常退出后,5秒内自动重启。看日志用journalctl -u yolov5.service -f,非常方便。
5.2 安全组和端口访问,别把服务暴露给全网
云服务器默认的安全组策略通常比较严格,但这不意味着你可以不小心放行所有端口。我见过有人为了省事,直接把8000端口对全网(0.0.0.0/0)开放,很快就被人扫描和试探。
正确的做法是:
- 如果服务只给内部调用,用内网IP访问,不要购买公网IP;
- 如果必须公网访问,安全组规则里只放行指定调用方的IP,不要整段开放;
- 端口号不要用默认的8000,改成一个不常见的高位端口,减少被扫描的概率。
Model接口服务面向内部系统时,配合简单的Token校验就够了,这能挡住绝大多数误触和垃圾扫描。
5.3 GPU的监控与自动告警思路
服务稳定运行不等于万事大吉,GPU也会出问题。温度过高时,推理速度会莫名变慢;显存泄漏严重时,进程会被系统杀掉。
我习惯用一个简单的cron脚本,每隔几分钟抓一次GPU状态:
nvidia-smi --query-gpu=utilization.gpu,temperature.gpu,memory.used --format=csv >> /var/log/gpu_monitor.log配合5.1节说的systemd自动重启,绝大多数小问题能自己恢复。如果要更精细的监控,可以配一个定时健康检查脚本,用curl请求你的服务接口,连续失败几次就重启服务。
这一步虽然不起眼,但真的能帮你省去很多半夜被电话叫醒的烦恼。
5.4 排错时最常用的三样工具
最后分享三个我实际排错时最常用的命令,基本覆盖了云服务器跑YOLOv5的大部分问题场景。
nvidia-smi:GPU状态第一现场,驱动、显存、温度、占用进程,一眼看全。ss -lntp:查端口监听状态,服务起没起来、端口被谁占用,它比netstat更轻快。journalctl -u yolov5.service -n 50:查服务日志,报错一般都能在这里看到完整堆栈。
实际工作中先看这三样,基本能定位90%的问题。剩下10%大概率是依赖版本冲突,重新检查一遍requirements.txt就好。
写到最后说点实在的。我前前后后给不少项目做过模型部署,最深的体会是:部署这件事,打磨到最后拼的不是会多少花活,而是把一条平平无奇的链路稳定跑住。每台机器环境都不一样,与其到处找“万能教程”,不如把Docker用起来,把环境做一次固化;先不要追求TensorRT那种极限性能,PyTorch跑通、systemd守护、GPU监控这三板斧,足以应对绝大多数线上场景。希望这篇内容能帮你少踩几个坑,早点睡个踏实觉。