免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

AI模型部署全攻略:从本地裸机到边缘端四种主流方式

AI模型部署全攻略:从本地裸机到边缘端四种主流方式 做过模型训练的朋友都有体会训练流程再复杂跑通脚本、看着loss降下来那一刻心里是有底的。真正让人心里没底的往往是模型训完之后那个环节——AI模型部署。模型文件躺在硬盘里它只是一堆权重离“能用”还差了十万八千里。作为AI训练师如果你想把模型真正交到业务方手里或者自己做个Demo上线跑起来你迟早要面对部署这件事。“AI训练师图解_10.2_四种主流方式_AI模型部署”这节内容就是把AI模型部署这件事掰开揉碎用图解的方式梳理出四条经典路线本地裸机部署、云端API服务化部署、容器化部署、边缘端轻量部署。每条路线适合什么场景、需要什么硬件、有什么坑下面我会结合实操经验一条一条讲清楚。如果你正处在“模型训练完了但不知道怎么上线”的阶段或者想从零搭起一套可用的推理服务这篇内容应该能让你少走不少弯路。1. 部署的四条路先看清全貌再动手1.1 为什么部署环节经常被低估很多从算法岗转过来的人最早对部署的理解就是“把模型文件发给对方对方自己跑起来”。这个想法在真实项目里基本行不通。训练和部署是两个完全不同的工程场景训练时你可以接受慢只要准确率够高部署时用户等不了三秒硬件环境也不是你说了算。训练是“把模型做出来”部署是“让模型在真实环境里稳定干活”。拿做饭打个比方训练像研究一道新菜厨房设备随便你用时间也不限部署像开一家餐馆灶台只有那么几个客人落座了你就得上菜而且每天口味不能飘。AI模型部署要做的事就是在各种受限环境下把模型推理的响应速度、吞吐量、稳定性都调到能交差的程度。1.2 四种主流方式的定位与适用边界按照“从简单到复杂、从通用到专用”的顺序主流的模型部署方式可以分成下面四种部署方式典型场景硬件门槛上手难度适用人群本地裸机部署个人开发机、实验验证、离线推理一张普通GPU或强CPU低刚接触部署的AI训练师云端API服务化部署Web应用、小程序后端、多端复用云服务器GPU实例中需要把模型交付给产品使用的团队容器化部署统一交付环境、私有化部署、NAS设备可支持Docker的设备中高需要跨环境部署的工程团队边缘端轻量部署手机、嵌入式设备、摄像头盒子低算力设备高做IoT、端侧智能的开发者这里要强调一个观点这四条路不是替代关系而是递进关系。同一个模型在开发阶段用本地部署验证上线阶段用API服务化交付阶段打包成容器如果产品形态是端侧设备再做边缘端优化。你完全可以一个项目里走完这四步每一步解决不同阶段的问题。2. 方式一本地裸机部署入门必走的路2.1 本地部署到底在部署什么本地裸机部署是最直观的一种方式你有一台带GPU的机器把训练好的模型权重加载到内存里写个推理脚本输入数据进去模型吐结果出来。整个过程不依赖外部网络不涉及复杂的服务架构。技术上看本地部署要解决三件事模型文件的加载、推理环境的搭建、输入输出数据的处理。模型文件通常是.pt、.pth、.h5这类格式需要对应的深度学习框架去读推理环境指的是PyTorch、TensorFlow、CUDA、cuDNN这些依赖库的版本组合数据处理的职责是把原始图片、文本转成模型能吃的张量并把模型输出的张量转回人能看懂的结果。很多AI训练师在本地部署时犯的第一个错误是直接把训练脚本拿来当推理脚本用。训练脚本里包含了数据增强、梯度计算、评估逻辑等一堆推理用不上的东西跑起来又慢又容易出错。正确做法是单独写一个轻量的推理脚本只保留模型加载、预处理、前向计算、后处理这四段逻辑。2.2 一个最小可用的本地推理示例以PyTorch为例一个最精简的本地推理脚本长这样import torch from PIL import Image from torchvision import transforms # 1. 加载模型权重 model torch.load(model.pth, map_locationcuda:0) model.eval() # 2. 定义预处理 preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 3. 读取并处理输入 img Image.open(test.jpg).convert(RGB) input_tensor preprocess(img).unsqueeze(0).cuda() # 4. 前向推理 with torch.no_grad(): output model(input_tensor) # 5. 后处理 pred_id torch.argmax(output, dim1).item() print(预测类别ID:, pred_id)这段代码看起来简单但有几个细节是新手容易忽略的。第一model.eval()必须调用它会关闭Dropout和BatchNorm的训练行为否则同一张图每次推理结果都可能不一样。第二推理要用torch.no_grad()包起来省掉梯度计算图的内存开销对大模型来说这一步能省下不少显存。第三unsqueeze(0)是在最前面加一个batch维度因为模型要求输入形状是[batch_size, channels, height, width]单张图也要凑成四维张量。2.3 本地部署的典型问题和排查思路本地部署最常见的问题就是显存不足OOM。一个比较实用的估算方式模型显存占用约等于参数量乘以精度字节数。比如一个70亿参数的大模型如果用FP16每个参数占2字节推理光权重就要占14GB显存再加上中间激活值、CUDA上下文实际需要的显存大概还要再乘1.2到1.5倍。如果遇到OOM优先做两件事一是把输入batch size调到1减少激活值存储二是检查有没有历史进程占着显存用nvidia-smi看一眼很直观。另一个常见问题是CPU推理极慢因为很多模型默认在CPU上也能跑但速度可能是GPU的几十倍。这时候要检查PyTorch到底用没用上GPU可以打印model.device如果显示cpu说明加载模型时没指定map_locationcuda:0。还有一个容易忽略的坑版本兼容性。用一个PyTorch 2.0环境训练的.pt文件拿到PyTorch 1.8环境里加载经常报RuntimeError: Picked up unsupported model version。所以本地部署时最好用conda单独建一个环境严格复现训练时的框架版本。3. 方式二云端API服务化把模型变成接口3.1 为什么要把模型变成API本地部署解决了“我能跑”的问题但解决不了“别人能用”的问题。你不可能把自己的电脑开放给所有用户访问也不可能让每个使用方都去配置一套深度学习环境。这时候就需要把模型包装成一个API服务用户发HTTP请求服务端调用模型推理再把结果返回给用户。调用方不需要关心模型是什么框架训练的只需要发请求、收结果。服务化部署的好处有三个。第一是解耦模型升级、训练代码重构都不影响上游业务方只要接口的输入输出格式不变第二是扩展并发量上来之后可以横向加多个服务实例前端加负载均衡第三是复用同一个模型服务可以同时供应Web端、小程序、App等多个客户端。这里也回应一个很常见的疑问为什么不直接调商业API而自己部署商业API确实省事但有几个现实问题数据隐私敏感数据不能出内网、成本高频调用费用可观、定制化微调后的模型没法在通用API里跑。所以只要模型是自研或微调过的自己部署API基本是必经之路。3.2 用FastAPI快速包一层推理服务Python生态里做推理服务我推荐FastAPI性能比Flask好自带接口文档而且写起来很简洁。一个基本的模型API服务包括四块模型加载、请求数据校验、推理逻辑、响应返回。一个典型的例子如下from fastapi import FastAPI, File, UploadFile import torch from PIL import Image from torchvision import transforms import io app FastAPI() model None app.on_event(startup) def load_model(): global model model torch.load(model.pth, map_locationcuda:0) model.eval() app.post(/predict) async def predict(file: UploadFile File(...)): # 读取图片 img_bytes await file.read() img Image.open(io.BytesIO(img_bytes)).convert(RGB) # 预处理 preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) input_tensor preprocess(img).unsqueeze(0).cuda() # 推理 with torch.no_grad(): output model(input_tensor) pred_id torch.argmax(output, dim1).item() return {pred_id: pred_id}然后在终端执行uvicorn main:app --host 0.0.0.0 --port 8000启动后浏览器访问http://localhost:8000/docs就能打开自动生成的接口文档可以直接在界面上传图片测试接口。生产环境部署时一般会在前面再加一层Nginx做反向代理和限流服务进程用gunicorn配合uvicorn的多worker模式来支撑并发。3.3 服务化部署的踩坑记录自己写推理服务的时候最容易踩的坑就是模型加载位置不对。新手容易把模型加载写在请求处理函数里结果是每次请求都重新load一遍模型慢到怀疑人生。正确做法像上面代码一样在服务启动事件里一次性加载到全局变量之后所有请求都复用。第二个坑是并发安全。PyTorch的模型在multi-thread环境里做推理如果不加锁可能出现显存冲突或结果错乱。FastAPI本身是异步框架但模型推理是CPU/GPU密集操作建议用threading.Lock包住推理部分或者用多进程部署多个worker。我在实际项目中遇到过并发超过10个请求之后服务直接卡死的情况最后定位就是并发推理导致的CUDA上下文问题。第三个坑是超时设置。有些模型推理一次要好几秒前端请求默认超时时间往往只有几秒很容易被误判为服务不可用。经验值是把请求超时设到模型最慢一次推理时间的3到5倍并且给推理服务加上健康检查接口/health方便上游做探活。4. 方式三容器化部署飞牛NAS这类场景怎么玩4.1 容器化到底解决了什么问题服务化部署做到后面你会发现一个尴尬的问题换一台机器部署环境又要重来一遍。CUDA版本、Python版本、依赖库版本任何一个对不上服务就跑不起来。容器化Docker就是来解决这个问题的把代码、依赖、系统库、模型文件全部打包进一个独立镜像里在任何装了Docker的机器上都能以相同方式运行。用一个例子说明你本地推理跑得好好的但客户那边只有一台裸机Linux没有Python环境没有CUDA更没有你用的那个PyTorch版本。如果用传统方式部署你需要写一长串安装文档然后祈祷客户每一步都照做。用容器化部署你只需要交付一个镜像文件客户执行docker run服务就起来了。环境一致性是容器化最大的价值。最近网上讨论度挺高的“飞牛部署AI模型”本质就是容器化的一个落地场景。飞牛私有云这类NAS设备本身硬件资源不算强但通过Docker可以把一个训练好的模型服务完整打包在NAS上运行既方便管理又能充分利用闲置算力。里面用的部署思路跟我们下面要讲的流程完全一致只是宿主环境从服务器变成了NAS设备。4.2 用Docker封装模型服务的完整流程把刚才那个FastAPI推理服务容器化核心是写一份Dockerfile。示例配置如下FROM pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY main.py . COPY model.pth . EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]构建镜像并启动容器docker build -t my-inference-service:latest . docker run -d --gpus all -p 8000:8000 --name inference my-inference-service:latest这里有几个经验点。第一基础镜像不要选pytorch/pytorch:latest这种变动的标签要锁死具体版本保证镜像可复现。第二--gpus all是在容器里启用GPU的开关没加这个参数的话容器里根本看不到显卡。第三模型文件可以打进镜像里如上例也可以挂载到外部飞牛NAS部署时更推荐挂载因为模型文件大、更新频繁每次改模型都要重新build镜像很费时间。挂载方法是在docker run时加参数-v /path/to/model:/app/model.pth。如果要在飞牛NAS上部署操作路径大概是这样的飞牛的套件中心装好Docker应用然后在管理界面里导入镜像或执行docker run命令把NAS里存放模型文件的目录挂载到容器内设置好端口映射就能通过局域网或公网访问推理服务。整个过程的重点是理解镜像和容器这两个概念其余操作跟服务器上完全一样。4.3 容器化部署的常见坑容器化部署最大的坑是镜像体积失控。一个PyTorch基础镜像可能就有四五个GB如果再叠加上模型文件推送和拉取都极其痛苦。解决办法一是换用更精简的基础镜像比如python:3.10-slim配合单独的依赖安装二是利用Docker的层缓存机制把不常变的依赖安装放在前面经常变的代码放在后面这样代码更新时不用重新下载依赖层。第二个坑是CUDA和驱动不匹配。容器内用的是CUDA运行库宿主机需要的是NVIDIA驱动两者之间存在版本匹配关系。常见报错是CUDA error: no kernel image is available for execution on the device一般是因为宿主机的NVIDIA驱动太老撑不起容器里新版本CUDA。检查方法是在宿主机执行nvidia-smi看驱动版本在容器内执行nvcc --version看CUDA版本两者差距过大的时候就调整基础镜像的CUDA版本。还有一个很多人忽略的问题时区。很多基础镜像默认是UTC时区日志时间和本地差8小时排查问题的时候特别容易误导。在Dockerfile里加一行ENV TZAsia/Shanghai就能解决。5. 方式四边缘端部署轻量化的实战打法5.1 边缘端部署的典型场景和约束边缘端部署是四种方式里最“硬核”的一种因为它的运行环境实在不友好没有GPU、没有大内存、甚至没有完整的操作系统可能是ARM架构的嵌入式板子也可能是手机芯片。典型场景包括智能摄像头里的目标检测模型、手机上的语音识别模型、工业网关上的异常检测模型。边缘端的核心矛盾是模型效果要好但算力、功耗、存储都有限。这就逼着AI训练师在部署阶段做“减法”要么换更小的模型结构要么对现有模型做量化压缩要么两者同时做。很多情况下还需要把深度学习框架的运行时也裁剪掉只用最精简的推理引擎去加载模型。5.2 量化与格式转换ONNX、TensorRT与INT8边缘端部署最常见的技术路线是先做格式转换再做量化压缩。格式转换的标准选择是ONNXOpen Neural Network Exchange它相当于深度学习模型的“通用语言”可以把PyTorch、TensorFlow训练的模型导成中间格式再由各平台的推理引擎来加载。以PyTorch导出ONNX为例代码很简洁import torch model torch.load(model.pth, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, model.onnx, opset_version11, input_names[input], output_names[output] )导出的ONNX模型可以用ONNX Runtimeonnxruntime来推理也可以用NVIDIA的TensorRT做深度优化。量化是在此基础上把模型权重从FP324字节压缩到INT81字节模型体积直接缩到四分之一推理速度往往能提升2到3倍。代价是精度会有轻微回退一般分类任务可以接受目标检测这类对边界框精度敏感的任务需要仔细评估。我用ONNX Runtime做边缘端推理的典型流程如下import onnxruntime as ort import numpy as np from PIL import Image sess ort.InferenceSession(model.onnx, providers[CPUExecutionProvider]) img Image.open(test.jpg).resize((224, 224)) input_data np.array(img).astype(np.float32) / 255.0 input_data np.transpose(input_data, (2, 0, 1))[None, ...] outputs sess.run(None, {input: input_data}) pred_id np.argmax(outputs[0], axis1).item()注意这里已经完全没有PyTorch了只有一个onnxruntime依赖占用资源非常小很适合塞进嵌入式设备。5.3 边缘端部署的注意事项边缘端部署最容易踩的坑是“算子不支持”。训练时模型里随便用了一个自定义层导出ONNX时报错或者推理引擎执行时报错都是家常便饭。排查方法是先打印ONNX模型的计算图看哪些算子不在目标推理引擎的支持列表里再用等价的标准算子替换或者干脆把自定义层改回纯PyTorch原语。第二个坑是动态形状。很多模型在训练时batch size是固定的导出时也写死了但边缘端输入尺寸不稳定比如摄像头画面分辨率变化。解决方案是在导出ONNX时设置动态轴torch.onnx.export里加dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}代价是推理性能会略有下降属于精度和灵活性的权衡。第三个坑是量化后精度崩了。出现这种情况优先观察是哪一类样本受影响最大。经验做法是先做校准集采样从真实业务数据里抽几百张有代表性的图片在量化工具里跑一遍统计激活值分布采样质量差的时候量化精度会崩得很难看。如果校准后精度还是不行就考虑只量化部分层保留对精度影响大的层为FP16。6. 选型决策、问题速查与个人心得6.1 怎么判断该用哪种部署方式面对一个具体需求选择部署方式可以从四个维度做判断硬件条件手里有什么算力有GPU服务器优先服务化或容器化只有NAS就考虑容器化只有手机就考虑边缘端量化。服务对象自用验证就本地部署给团队内部用就API服务化要交付到客户现场就容器化要卖硬件就边缘端。性能要求对延迟极度敏感毫秒级就要边缘端或专用推理引擎优化容忍秒级响应就用云端服务化。运维能力团队有运维人力可以接受容器化、K8s这套没有的话优先考虑托管的云API或简单的服务化方案。这四类问题问下来基本就能圈定一个主方案。如果需求覆盖多个场景那就把方案拆开核心服务用容器化对外接口用API端侧功能走边缘端。6.2 实测中常见问题速查表下面的表格整理了我实际部署过程中遇到的高频问题基本覆盖了四种方式的典型故障点问题现象可能原因解决办法推理报CUDA out of memory模型权重加激活值超出显存调低batch size、换FP16、用torch.no_grad()同一张图多次推理结果不一样忘记调model.eval()推理前调用model.eval()API服务并发稍高就卡死多线程同时调GPU推理加锁、多进程worker、串行化推理容器启动后无法用GPUdocker run没加--gpus all添加GPU参数并确认驱动匹配导入镜像后运行报CUDA版本错误容器CUDA与宿主机驱动不匹配换低版本CUDA基础镜像、升级驱动ONNX推理报Unsupported Operator算子在推理引擎里不支持替换成支持的标准算子、降低opset版本量化后精度大幅下降校准集数据分布偏差大增加校准集样本量、部分层保持FP16请求经常超时模型推理时间超过网关超时增大超时阈值、做推理结果缓存6.3 从AI训练师视角出发的几条实操建议项目走得多了我自己总结了几条习惯不一定适合所有人但确实帮我省了很多事。第一条习惯是“先跑通再优化”。很多人一开始就纠结用TensorRT还是ONNX Runtime纠结来纠结去一天过去了模型还没跑起来。我的做法是最粗暴的方式先让模型吐出结果哪怕慢一点确认模型本身没问题再逐步做性能优化。每一步只改一个变量出了性能问题也定位得快。第二条习惯是“部署文档和训练代码一样重要”。交接模型的时候光给一个.pth文件等于啥也没给。至少要包含训练时的框架版本、模型输入输出格式、推荐部署方式、验证集上能达成的效果基线。有了这些信息接手方才能判断部署结果对不对。第三条习惯是“每次模型更新都要做回归测试”。换了一个新权重文件之后不要只测一两张图觉得没问题就上线。最好准备一个固定的测试集跑一遍对比新旧版本的预测结果确认没有大面积行为漂移。部署方式和代码不变的情况下模型更新导致的线上事故九成都是省了这一步。最后想说的是AI模型部署这件事看起来是纯工程问题但做久了你会发现它非常考验AI训练师对模型本身的理解你越清楚模型的输入输出边界、依赖算子和精度冗余部署起来就越游刃有余。这四种方式不是考试大纲是工具。掌握它们你就能在合适的场景用合适的工具把你的模型真正送到用户手里。
返回列表