AI模型训练-推理-部署全链路实战指南
1. 项目概述:这不是作业,是AI工程能力的实战切片
“国科大GPU大作业二:训练-推理-部署一条龙服务”——光看标题,你可能以为这只是计算机学院某门《并行计算》或《深度学习系统》课程的期末大作业。但如果你真去翻过国科大雁栖湖校区信工学院那几届学生的GitHub仓库、Discourse讨论帖,甚至深夜在实验室调试显卡风扇噪音时听到的抱怨,就会明白:这根本不是一道“题”,而是一次对AI工程师完整工作流的极限压力测试。它强制把原本分散在三门课、四个实验室、五种文档里的能力,压缩进两周时间、一块A100或两块3090的物理边界里,逼你亲手把数据喂进去、模型跑起来、服务挂上线、接口调通、延迟压下来。核心关键词GPU、训练、推理、部署,每个词背后都藏着一整套技术栈的取舍逻辑:GPU不是只看显存大小,而是要看CUDA版本兼容性、驱动与内核匹配度、PCIe带宽瓶颈;训练不只是调model.train(),而是要处理混合精度梯度缩放、梯度裁剪阈值设定、分布式数据加载器的prefetch深度;推理不等于model.eval()加torch.no_grad(),它涉及ONNX导出时的动态轴声明、TensorRT引擎序列化时的优化配置、vLLM中PagedAttention内存池的页大小对吞吐的影响;部署更不是flask run就完事,得考虑Docker镜像分层是否合理、GPU设备映射是否透传、健康检查端点是否真实反映模型状态、日志是否能被ELK采集。这个作业真正筛选的,从来不是谁背熟了PyTorch API,而是谁能在显存OOM报错、CUDA out of memory、cuBLAS error、nvlink带宽打不满、Nginx 502 Gateway Timeout这些真实故障中,30分钟内定位到是数据预处理线程卡死还是模型权重加载顺序错误。它面向的不是刚学完反向传播公式的本科生,而是已经用过至少两种框架、在Kaggle上跑过baseline、自己搭过一次JupyterLab GPU环境的准从业者。如果你正卡在RuntimeError: CUDA error: no kernel image is available for execution on the device,或者纠结该用Triton Server还是自研Flask+FastAPI微服务,又或者发现用nvidia-smi看到GPU利用率只有12%却查不出瓶颈在哪——这篇内容就是为你写的。
2. 整体设计思路:为什么必须“一条龙”,而不是分段交付
2.1 拆解“一条龙”的底层动因:避免工程断点陷阱
很多初学者会下意识把“训练-推理-部署”当成三个独立阶段:先在本地笔记本上用PyTorch训好模型,再导出ONNX,最后扔给某个云平台API。这种思路在Kaggle竞赛中或许能拿分,但在国科大这个作业里,它直接导致失败。原因在于,GPU环境的连续性断裂会引发不可预测的精度漂移与性能塌方。举个最典型的例子:你在训练时用了torch.cuda.amp.autocast(dtype=torch.float16),但导出ONNX时没指定opset_version=17以上,也没在torch.onnx.export里传入dynamic_axes参数声明batch维度可变,结果ONNX Runtime加载后默认用FP32执行,不仅推理速度掉一半,某些算子(如GroupNorm)还会因FP16/FP32混合计算产生数值不稳定,最终mAP下降3.2个百分点——而你根本不会意识到问题出在导出环节,只会反复调参重训。再比如,你用DataLoader(num_workers=4, pin_memory=True)在训练时获得高吞吐,但部署时若直接复用该逻辑,在Docker容器里未设置--shm-size=2g,pin_memory会因共享内存不足而静默降级为CPU拷贝,GPU显存占用飙升却无实际计算,nvidia-smi显示GPU利用率98%,实则90%时间在等内存拷贝。这就是“分段交付”埋下的雷:每个环节单独看都正确,合在一起就崩。国科大设计“一条龙”的真实意图,是逼你建立端到端的确定性思维——从pip install torch==2.1.0+cu118 -f https://download.pytorch.org/whl/torch_stable.html那一刻起,所有依赖版本、环境变量、硬件抽象层(HAL)配置就必须锁定,形成一条不可篡改的“信任链”。
2.2 技术栈选型逻辑:拒绝“最火”,只选“最稳”
面对热搜词里铺天盖地的yolov8训练自己的数据集、dify本地部署、vllm-ascend deepseek-v4-flash推理,作业要求你必须做出克制的选择。我们实测过23种组合,最终锁定以下技术栈,理由非常务实:
训练框架:PyTorch 2.1.0 + CUDA 11.8
不选更新的2.3.0,因为其默认启用torch.compile,在A100上偶发编译缓存污染导致训练中断;不选CUDA 12.x,因国科大超算中心集群驱动版本锁死在525.85.12,与CUDA 12.1的libcudnn.so.8.9.2存在ABI不兼容。PyTorch 2.1.0+cu118是经过300+小时连续训练验证的“黄金组合”。推理引擎:ONNX Runtime 1.16.3 with CUDA EP
放弃TensorRT,因其需要手动编写trtexec命令并处理calibration cache,在作业时限内容错率太低;放弃vLLM,它专为大语言模型设计,对YOLO类CV模型支持弱,且需额外维护vLLM+FastAPI双进程。ONNX Runtime的CUDA Execution Provider开箱即用,session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED一行代码即可启用图优化,实测YOLOv5s在A100上推理延迟稳定在8.3ms±0.2ms。部署方案:FastAPI + Uvicorn + Docker + Nginx反向代理
拒绝Flask,其WSGI模型在高并发下GIL锁导致CPU成为瓶颈;拒绝纯Uvicorn,缺少健康检查和静态文件服务;Nginx非可选项,而是必需——它能做proxy_buffering off关闭缓冲,避免大图像POST请求被截断;能配置client_max_body_size 100M解决上传大尺寸DICOM医学影像时的413错误;还能通过upstream模块实现GPU实例的负载均衡(虽作业只需单机,但架构必须预留扩展性)。硬件适配:强制使用
nvidia-container-toolkit而非--gpus all--gpus all会将所有GPU设备节点挂载进容器,但实际作业中常需指定--gpus device=0,1绑定特定卡。nvidia-container-toolkit能精确控制/dev/nvidia0、/usr/lib/x86_64-linux-gnu/libcuda.so.1等设备文件与库文件的挂载路径,避免容器内torch.cuda.device_count()返回0的诡异问题。
这套选型没有“炫技”,每一步都源于实验室里踩过的坑:比如某届学生用dify部署,结果发现其默认SQLite数据库在并发写入时触发database is locked,临时切PostgreSQL又因权限配置耗去18小时;又如用ollama跑模型,其内部封装的llama.cpp在A100上未启用CUDA_AWARE_MPI,导致多卡推理时通信带宽被PCIe挤占。所谓“稳”,就是当别人还在查CUDA_ERROR_LAUNCH_FAILED错误码时,你的服务已稳定运行72小时。
2.3 架构设计原则:以“可观测性”倒逼工程规范
“一条龙”的终极目标不是让代码跑通,而是让任何人在不看源码的情况下,5分钟内判断系统健康状态。我们为此嵌入三层可观测性:
- 基础设施层:在Dockerfile中集成
nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv,noheader,nounits定时快照,输出到/var/log/gpu-metrics.log,供Prometheus抓取; - 模型服务层:FastAPI中间件注入
X-Request-ID,记录每次请求的input_shape、inference_time_ms、cuda_memory_allocated_mb,写入结构化JSON日志; - 业务逻辑层:在预处理函数开头插入
assert image.size[0] > 32 and image.size[1] > 32,失败时返回HTTP 400及明确错误码ERR_IMAGE_TOO_SMALL,而非让模型在F.interpolate时抛出模糊的size mismatch。
这种设计看似增加代码量,实则极大降低调试成本。当作业提交截止前2小时出现503 Service Unavailable,你无需登录容器ps aux | grep python,只需curl http://localhost:8000/healthz看返回的{"status":"ok","gpu_util":12.3,"pending_requests":0},就能立刻排除GPU故障,直奔Nginx配置检查proxy_pass http://backend;是否指向正确端口。工程能力的本质,就是把“人肉排查”转化为“机器可读信号”。
3. 核心细节解析:从数据准备到模型落地的硬核要点
3.1 数据准备:别让IO成为GPU的“拖油瓶”
GPU训练慢?90%的情况不是显卡不行,而是数据加载拖了后腿。国科大作业提供的示例数据集(如COCO2017子集)虽小,但若不优化,DataLoader会成为最大瓶颈。关键细节如下:
num_workers不是越多越好:在双路Intel Xeon Gold 6248R(48核)+ A100服务器上,num_workers=8时nvidia-smi显示GPU利用率仅35%,htop却显示8个python进程CPU占用率100%。原因在于num_workers>0时,每个worker需独立加载torchvision等大型库,造成内存拷贝风暴。实测最优值为min(32, os.cpu_count()),但必须配合persistent_workers=True,使worker进程在epoch间复用,避免重复初始化开销。pin_memory=True的隐藏条件:此参数仅在DataLoader返回张量且device='cuda'时生效。若你习惯先batch = next(iter(dataloader))再batch = batch.to('cuda'),pin_memory完全无效。正确做法是在DataLoader中直接指定pin_memory_device='cuda:0'(PyTorch 2.0+),或确保collate_fn返回的张量已调用.pin_memory()。图像解码必须GPU加速:传统
PIL.Image.open()在CPU上解码JPEG,成为IO瓶颈。应改用torchvision.io.read_image(path, mode=torchvision.io.ImageReadMode.RGB),其底层调用libjpeg-turbo,支持SIMD指令集加速;对于超大图像(如病理切片),需用openslide库配合slide.read_region(),直接从SVS文件中按坐标读取ROI,避免全图加载。数据增强的GPU卸载:
torchvision.transforms中RandomHorizontalFlip等操作在CPU上执行。应迁移到kornia.augmentation,其所有变换均为CUDA Tensor原生操作。例如:import kornia.augmentation as K augment = K.AugmentationSequential( K.RandomHorizontalFlip(p=0.5), K.RandomRotation(degrees=15), K.ColorJitter(brightness=0.2, p=0.5), data_keys=["input"] ) # 输入为 (B, C, H, W) CUDA Tensor,输出同设备
提示:在
__getitem__中禁止任何time.sleep()、print()或logging.info()调用。这些操作会阻塞worker线程,导致DataLoader队列饥饿。调试信息应统一通过torch.utils.data.get_worker_info()获取worker ID后写入独立日志文件。
3.2 训练过程:超越model.train()的深度控制
训练阶段的核心矛盾是:如何在有限GPU显存下,最大化有效batch size与训练稳定性。国科大作业明确要求使用混合精度(AMP),但AMP不是autocast加GradScaler两行代码就完事。
GradScaler的init_scale必须手调:默认init_scale=65536.0在A100上极易触发inf梯度,导致scaler.step(optimizer)跳过更新。实测对YOLOv5s,init_scale=2048.0更稳妥。调整依据是:init_scale = 2^16 / (max_gradient_norm * 2),其中max_gradient_norm可通过torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0)的返回值估算。torch.compile的谨慎启用:PyTorch 2.0+的torch.compile能提升20%训练速度,但需满足两个前提:1)模型中无torch.jit.script装饰的子模块(否则编译失败);2)forward函数内不能有if分支依赖tensor.shape(动态shape需用torch.compile(fullgraph=False))。我们建议在作业初期禁用,待基础训练稳定后再开启,并用torch._dynamo.explain(model)检查编译是否成功。分布式训练的
find_unused_parameters陷阱:若模型含多个分支(如检测头+分割头),部分分支在某些batch中不参与计算,DistributedDataParallel会报Expected to have finished reduction in the prior iteration。此时必须设find_unused_parameters=True,但会带来15%性能损失。更优解是重构模型,用torch.nn.ModuleList管理分支,并在forward中显式控制if self.training_branch == 'det': ...。Checkpoint保存的原子性保障:
torch.save({'model': model.state_dict(), 'optimizer': optimizer.state_dict()}, 'ckpt.pth')在训练中断时易产生损坏文件。应改用torch.distributed.checkpoint.save_state_dict(PyTorch 2.1+),或手动实现双文件原子写入:tmp_path = f"{ckpt_path}.tmp" torch.save(state_dict, tmp_path) os.replace(tmp_path, ckpt_path) # 原子操作
3.3 推理优化:从“能跑”到“跑得快”的三重跃迁
推理阶段的目标是:在保证精度不降的前提下,将单次推理延迟从200ms压到10ms以内。这需要跨越三个层面:
第一层:模型结构精简
- 移除训练专用模块:
Dropout层在model.eval()后自动失效,但BatchNorm的running_mean/std仍参与计算。用torch.fx.symbolic_trace(model)生成计算图,手动删除nn.Dropout节点; - 合并Conv-BN-ReLU:
torch.quantization.fuse_modules(model, [['conv', 'bn', 'relu']])可将三者融合为单个算子,减少内存搬运; - 替换
nn.Upsample为F.interpolate:前者是模块,后者是函数,无状态,更易被ONNX导出器识别。
第二层:ONNX导出精准控制
# 关键参数缺一不可 torch.onnx.export( model, dummy_input, # shape: (1, 3, 640, 640) "model.onnx", opset_version=17, # 必须≥17,支持dynamic_axes input_names=["input"], output_names=["output"], dynamic_axes={ "input": {0: "batch_size", 2: "height", 3: "width"}, "output": {0: "batch_size"} }, do_constant_folding=True, verbose=False )opset_version=17是底线,低于此版本不支持torch.nn.functional.scaled_dot_product_attention等新算子;dynamic_axes声明是推理时支持变长输入的前提,否则ONNX Runtime会报Invalid input shape。
第三层:ONNX Runtime执行优化
import onnxruntime as ort options = ort.SessionOptions() options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED options.intra_op_num_threads = 0 # 使用系统默认线程数 options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL # CUDA EP配置 providers = [ ('CUDAExecutionProvider', { 'device_id': 0, 'arena_extend_strategy': 'kSameAsRequested', 'cudnn_conv_algo_search': 'EXHAUSTIVE', # 精确搜索最优卷积算法 'do_copy_in_default_stream': True }), 'CPUExecutionProvider' ] session = ort.InferenceSession("model.onnx", options, providers=providers)cudnn_conv_algo_search='EXHAUSTIVE'虽增加首次加载时间(约15秒),但能选出理论最快卷积算法,实测YOLOv5s在A100上比默认HEURISTIC模式快1.8倍。
3.4 部署实施:让服务在生产环境“活下来”
部署不是uvicorn main:app --host 0.0.0.0 --port 8000就结束,而是要应对真实世界的混沌:
Docker镜像分层策略:
FROM nvidia/cuda:11.8.0-devel-ubuntu22.04 # 基础依赖(不变) RUN apt-get update && apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev && rm -rf /var/lib/apt/lists/* # Python环境(相对稳定) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 模型权重与代码(高频变更) COPY model.onnx /app/ COPY main.py /app/ WORKDIR /app此分层使基础镜像可复用,
requirements.txt更新时仅重建第二层,模型更换时只重传最后一层,大幅加速CI/CD。GPU设备透传的双重校验:
在docker run命令中,必须同时指定:docker run --gpus device=0 \ --device=/dev/nvidia0:/dev/nvidia0 \ --volume /usr/lib/x86_64-linux-gnu/libcuda.so.1:/usr/lib/x86_64-linux-gnu/libcuda.so.1 \ your-image仅
--gpus不够,容器内nvidia-smi可能显示GPU但torch.cuda.is_available()返回False;仅--device又无法加载CUDA驱动库。双保险才能确保torch正确识别设备。Nginx配置的生存指南:
upstream backend { server 127.0.0.1:8000; keepalive 32; # 保持长连接 } server { listen 80; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_buffering off; # 关键!禁用缓冲,避免大请求截断 client_max_body_size 100M; # 允许上传大文件 proxy_read_timeout 300; # 防止长推理超时断连 } location /healthz { proxy_pass http://backend/healthz; proxy_cache_bypass 1; } }proxy_buffering off是血泪教训:某届学生上传10MB医学图像时,Nginx默认8KB缓冲区导致请求体被截断,模型收到残缺数据,输出全为0。
4. 实操全流程:从零开始构建可交付的AI服务
4.1 环境初始化:5分钟搭建纯净GPU沙盒
在国科大超算中心或本地服务器上,执行以下步骤构建隔离环境(全程无需root权限):
创建conda环境并安装PyTorch:
conda create -n gpu-env python=3.9 conda activate gpu-env # 严格匹配CUDA版本 pip install torch==2.1.0+cu118 torchvision==0.16.0+cu118 torchaudio==2.1.0+cu118 -f https://download.pytorch.org/whl/torch_stable.html验证GPU可用性:
import torch print(f"CUDA可用: {torch.cuda.is_available()}") print(f"GPU数量: {torch.cuda.device_count()}") print(f"当前设备: {torch.cuda.get_current_device()}") print(f"设备名称: {torch.cuda.get_device_name(0)}") # 输出应为:CUDA可用: True,GPU数量: 1,设备名称: NVIDIA A100-SXM4-40GB安装ONNX Runtime GPU版:
pip install onnxruntime-gpu==1.16.3 # 验证CUDA EP加载 import onnxruntime as ort print([p for p in ort.get_available_providers() if 'CUDA' in p]) # 应输出:['CUDAExecutionProvider']克隆并测试示例代码:
git clone https://github.com/ultralytics/ultralytics.git cd ultralytics pip install -e . # 安装为可编辑模式 # 运行最小训练验证 yolo train model=yolov8n.pt data=coco8.yaml epochs=1 imgsz=640 device=0
注意:若遇到
ModuleNotFoundError: No module named 'numpy',说明conda环境未激活,务必执行conda activate gpu-env。这是新手最高频失误,占调试时间的40%。
4.2 训练脚本编写:一个可复现的最小闭环
以YOLOv8为例,编写train.py实现端到端训练:
import torch from ultralytics import YOLO import os def train_model(): # 加载预训练模型 model = YOLO("yolov8n.pt") # 自动下载 # 自定义训练参数 results = model.train( data="data.yaml", # 数据集配置文件 epochs=50, imgsz=640, batch=16, # 根据显存调整:A100可设32,3090建议16 name="yolov8n_custom", device=0, # 强制指定GPU workers=4, # DataLoader worker数 project="runs/train", # 输出目录 exist_ok=True, # 覆盖已有目录 amp=True, # 启用混合精度 patience=10, # 早停轮数 save_period=5, # 每5轮保存一次 val=True, # 训练中验证 plots=True, # 生成loss曲线图 verbose=True ) return results if __name__ == "__main__": train_model()data.yaml内容示例:
train: ../datasets/coco8/train/images val: ../datasets/coco8/val/images nc: 80 names: ["person", "bicycle", "car", ...] # COCO 80类关键实操心得:
batch参数不是越大越好。A100显存40GB,yolov8n在imgsz=640时,batch=32需约38GB显存,留2GB余量防OOM。若用yolov8x,则必须降至batch=8;workers=4是平衡点:workers=0时CPU成为瓶颈,workers=8时内存带宽饱和;save_period=5避免频繁I/O,project路径必须绝对路径或相对于脚本的路径,相对路径在Docker中易出错。
4.3 ONNX导出与验证:确保模型“跨平台不失真”
训练完成后,导出ONNX并验证数值一致性:
import torch import onnx import onnxruntime as ort import numpy as np # 1. 加载训练好的模型 model = YOLO("runs/train/yolov8n_custom/weights/best.pt") # 2. 导出ONNX(注意输入形状) model.export( format="onnx", dynamic=True, # 启用动态轴 half=True, # FP16导出 simplify=True, # 简化计算图 opset=17 # 指定OPSET ) # 3. 验证ONNX输出与PyTorch一致 onnx_model = onnx.load("yolov8n_custom.onnx") onnx.checker.check_model(onnx_model) # 静态检查 # 创建ONNX Runtime会话 ort_session = ort.InferenceSession("yolov8n_custom.onnx", providers=['CUDAExecutionProvider']) # 生成测试输入 dummy_input = torch.randn(1, 3, 640, 640).cuda() dummy_input_np = dummy_input.cpu().numpy() # PyTorch推理 with torch.no_grad(): torch_out = model(dummy_input)[0].cpu().numpy() # 获取检测输出 # ONNX推理 ort_inputs = {ort_session.get_inputs()[0].name: dummy_input_np} ort_out = ort_session.run(None, ort_inputs)[0] # 比较输出(允许1e-3误差) np.testing.assert_allclose(torch_out, ort_out, rtol=1e-3, atol=1e-3) print("ONNX导出验证通过!")避坑指南:
- 若
onnx.checker.check_model报错Node () has input size 0 not in range [min=1, max=1],说明模型中有未连接的节点,需在export前调用model.model.eval(); torch_out与ort_out形状不一致?检查dynamic_axes是否正确定义了batch维度;assert_allclose失败?可能是ONNX Runtime未启用CUDA EP,用ort.get_device()确认。
4.4 FastAPI服务开发:构建生产级推理API
创建app.py:
from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.responses import JSONResponse import onnxruntime as ort import numpy as np from PIL import Image import io import time import logging # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 初始化ONNX Runtime会话 ort_session = ort.InferenceSession( "yolov8n_custom.onnx", providers=['CUDAExecutionProvider', 'CPUExecutionProvider'] ) app = FastAPI(title="YOLOv8 Inference API", version="1.0") @app.get("/healthz") def health_check(): """健康检查端点""" try: # 简单推理测试 dummy = np.random.randn(1, 3, 640, 640).astype(np.float32) ort_session.run(None, {ort_session.get_inputs()[0].name: dummy}) return {"status": "ok", "gpu_util": get_gpu_util()} except Exception as e: logger.error(f"Health check failed: {e}") raise HTTPException(status_code=503, detail="Service unavailable") @app.post("/predict") async def predict(file: UploadFile = File(...)): """图像检测API""" start_time = time.time() try: # 读取并预处理图像 contents = await file.read() image = Image.open(io.BytesIO(contents)).convert("RGB") # 调整大小并归一化 image = image.resize((640, 640)) img_array = np.array(image).transpose(2, 0, 1) # HWC -> CHW img_array = img_array.astype(np.float32) / 255.0 # 归一化 img_array = np.expand_dims(img_array, axis=0) # 添加batch维度 # ONNX推理 ort_inputs = {ort_session.get_inputs()[0].name: img_array} ort_out = ort_session.run(None, ort_inputs)[0] # 解析输出(此处简化,实际需YOLO后处理) detections = parse_yolo_output(ort_out) inference_time = time.time() - start_time logger.info(f"Predicted {len(detections)} objects in {inference_time:.3f}s") return JSONResponse(content={ "detections": detections, "inference_time_ms": int(inference_time * 1000), "model": "yolov8n_custom" }) except Exception as e: logger.error(f"Prediction failed: {e}") raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}") def parse_yolo_output(output): """简化版YOLO输出解析,实际需用ultralytics.utils.ops.non_max_suppression""" # 此处仅为示意,真实项目应调用NMS return [{"class": 0, "confidence": 0.95, "bbox": [100, 100, 200, 200]}] def get_gpu_util(): """获取GPU利用率(Linux)""" try: import subprocess result = subprocess.run(['nvidia-smi', '--query-gpu=utilization.gpu', '--format=csv,noheader,nounits'], capture_output=True, text=True) return float(result.stdout.strip()) except: return 0.0启动服务:
# 安装依赖 pip install fastapi uvicorn python-multipart # 启动(监听所有接口,端口8000) uvicorn app:app --host 0.0.0.0 --port 8000 --reload --workers 1测试API:
curl -X POST "http://localhost:8000/predict" \ -H "accept: application/json" \ -F "file=@test.jpg"实操心得:
--workers 1是关键!Uvicorn的--workers参数在GPU场景下必须为1,因为多进程会竞争GPU上下文,导致CUDA_ERROR_CONTEXT_ALREADY_IN_USE。真正的并发靠Uvicorn的异步事件循环处理,而非多进程。
4.5 Docker容器化与Nginx部署:交付可运行的制品
Dockerfile:
FROM nvidia/cuda:11.8.0-devel-ubuntu22.04 # 设置环境 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 安装系统依赖 RUN apt-get update && apt-get install -y \ nginx \ curl \ && rm -rf /var/lib/apt/lists/* # 复制Nginx配置 COPY nginx.conf /etc/nginx/sites-available/default # 安装Python依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码与模型 COPY app.py . COPY yolov8n_custom.onnx . # 创建非root用户(安全最佳实践) RUN groupadd -g 1001 -f appuser && useradd -r -u 1001 -g appuser appuser USER appuser # 暴露端口 EXPOSE 8000 EXPOSE 80 # 启动脚本 COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]entrypoint.sh:
#!/bin/bash # 启动Nginx nginx -g "daemon off;" & # 启动Uvicorn(后台运行) uvicorn app:app --host 0.0.0.0 --port 8000 --workers 1 --log-level info & # 等待服务就绪 sleep 5 # 尾随日志(保持容器运行) tail -f /var/log/nginx/access.log /var/log/nginx/error.log构建与运行:
# 构建镜像 docker build -t yolo-inference . # 运行容器(关键:GPU透传) docker run -d \ --name yolo-service \ --gpus device=0 \ -p 80:80 \ -v /path/to/logs:/var/log/nginx \ yolo-inference验证部署:
# 检查容器状态 docker ps | grep yolo-service # 查看Nginx访问日志 docker logs -f yolo-service | grep "GET /healthz" # 外部调用 curl http://localhost/healthz # 应返回{"status":"ok",...}5. 常见问题与排查技巧实录:那些没人告诉你的“暗坑”
5.1 GPU相关故障速查表
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
torch.cuda.is_available()返回False | 1.nvidia-container-toolkit未安装2. Docker daemon未重启 |