1. 从零上手华为云 ModelArts:一个后端工程师的踩坑实录
去年年底,团队接了一个图像分类的需求,客户指定要跑在华为云上。我在此之前一直用的是本地显卡+conda那套土办法,对云平台的训练流程说实话是有点抵触的——总觉得配置繁琐、文档晦涩、出了问题不好排查。但项目压着,硬着头皮上,前后折腾了大概两周,从数据上传、Notebook调试、训练作业提交,到最终用在线服务把模型部署成一个可调用的API,整个链路算是跑通了。这篇笔记就是那两周的完整复盘,把 ModelArts 模型训练与部署这条主线上的关键节点、参数选择、踩过的坑,以及一些文档里不会写的经验,尽量讲透。
如果你也是后端或者算法方向,手头有华为云的资源,想把自己的模型从本地搬到云上跑训练、再部署成服务,那这篇内容应该能帮你省掉不少试错时间。我会从整体设计思路讲起,然后拆解数据准备、训练配置、部署上线这几个核心环节,最后把我遇到的一堆报错和排查过程整理成速查表。全程不堆概念,只讲我实际怎么操作的、为什么这么操作。
需要先说明一点:ModelArts 的界面和功能迭代比较快,我写的是我实际操作时的版本和路径,如果你看到的界面和我描述的有出入,以你控制台上的实际入口为准,但底层的逻辑和参数含义是相通的。
2. 整体方案设计与选型思路拆解
2.1 为什么选 ModelArts 而不是自己搭训练环境
先说选型。当时摆在面前的有三条路:一是自己买几台带显卡的云主机,手动装 CUDA、cuDNN、PyTorch,自己管调度;二是用 ModelArts 这类托管平台;三是本地跑完再上传模型文件。
自己搭环境这条路我最早试过,最大的问题是环境一致性。本地调试用的 PyTorch 版本、CUDA 版本,和云主机上装的经常对不上,一个torch.cuda.is_available()返回 False 就能卡你半天。而且训练任务排队、显存监控、断点续训这些都得自己写脚本维护,对于我这种主要精力在后端、算法只是兼顾的人来说,维护成本太高。
ModelArts 的核心价值在于它把算力调度、环境镜像、训练日志、模型管理这几件事打包了。你选一个预置镜像,里面 PyTorch、TensorFlow、MindSpore 都装好了,CUDA 版本也是配好的,直接开 Notebook 就能跑。训练作业提交后,平台负责拉起实例、跑完自动释放,按需计费。对于中小规模、迭代频繁的训练任务,这个模式省心太多。
提示:ModelArts 的计费是按实际使用的算力时长算的,Notebook 实例只要开着就在计费,哪怕你没跑代码。我第一周就因为忘了关 Notebook,白白烧掉了几十块钱的算力。养成用完就停的习惯,或者用训练作业代替长时间挂着的 Notebook。
2.2 训练与部署的整体链路设计
整个链路我拆成了四段,这也是我推荐给新手的推进顺序:
- 数据准备:把原始数据传到对象存储 OBS,做好目录划分和标注格式转换。
- 开发调试:用 Notebook 做小批量数据验证、模型结构调试、单卡跑通。
- 正式训练:把调通的代码整理成训练脚本,提交训练作业,用多卡或更高规格的算力跑全量数据。
- 模型部署:把训练产出的模型文件注册到模型管理,创建在线服务,暴露成 API。
这个顺序的关键在于先小后大。很多人一上来就提交全量数据的训练作业,结果跑了两个小时发现数据路径写错了,或者某个依赖没装,算力全浪费。我的做法是先在 Notebook 上用几百张图跑通一个 epoch,确认 loss 在下降、没有报错,再提交正式作业。
关于部署方式,ModelArts 提供了在线服务、批量服务、边缘服务几种。我这个需求是实时推理,所以选的是在线服务,它会给你一个 HTTPS 的调用地址,后端直接发请求就行。如果你是要处理离线的一大批数据,批量服务更划算。
2.3 镜像与算力规格的选择逻辑
镜像选择上,我强烈建议新手先用预置镜像,不要一上来就自己写 Dockerfile。预置镜像里常用的框架版本都齐了,选一个和你本地开发环境接近的版本即可。我本地是 PyTorch 1.11,就选了对应的预置镜像,省去了装依赖的麻烦。
算力规格这块,ModelArts 提供了从单卡到多卡的多种规格。选择依据主要是显存和训练时长。我的数据集大概两万张图,ResNet50 的输入尺寸 224x224,batch size 设 32,单张 V100 的显存占用大概在 8G 左右,完全够用。如果你要跑更大的模型或者更大的 batch,就得往上选。这里有个经验:先按最小可用规格跑通,再根据实际显存占用和训练速度决定要不要升配,不要凭感觉选最贵的。
3. 数据准备与 OBS 存储的实操细节
3.1 数据上传到 OBS 的正确姿势
ModelArts 的数据读写默认走对象存储 OBS,所以第一步是把数据传上去。OBS 的桶(Bucket)要选和你的 ModelArts 实例同一个区域,否则跨区域读取会慢很多,甚至产生额外的流量费用。我一开始桶建在了华北,实例开在了华东,读数据的时候那个慢啊,后来重新建桶才解决。
上传方式有几种:控制台网页上传适合小数据量,数据量大就用 OBS Browser+ 客户端或者 obsutil 命令行工具。我用的是 obsutil,因为它支持断点续传和批量上传。基本命令长这样:
# 配置访问密钥 obsutil config -i=你的AK -k=你的SK -e=obs.cn-east-3.myhuaweicloud.com # 上传整个目录 obsutil cp -r -f ./dataset obs://your-bucket/dataset/-r是递归,-f是强制覆盖。上传大文件的时候建议加上-p=10开多线程,速度能快不少。
注意:AK/SK 是访问密钥,千万不要硬编码在代码里提交到代码仓库。我一般用环境变量或者配置文件的方式管理,本地开发用
~/.obsutilconfig,代码里读环境变量。
3.2 数据集目录结构与标注格式
目录结构这块,我踩过一个坑。ModelArts 的自动学习功能对目录结构有固定要求,但如果你是自己写训练脚本,目录结构就随意了,只要你的代码能正确读到就行。我采用的是最经典的 ImageFolder 风格:
dataset/ ├── train/ │ ├── cat/ │ └── dog/ └── val/ ├── cat/ └── dog/每个类别一个文件夹,文件夹名就是标签。这种结构 PyTorch 的ImageFolder能直接读,不用自己写 Dataset 类,省事。
如果你的标注是 XML 或者 JSON 格式(比如从 LabelImg 导出的),需要先转成这种目录结构,或者自己写 Dataset 类解析。我建议在本地就把数据整理干净再上传,因为云上改数据比本地麻烦得多。本地用脚本批量处理,处理完检查一遍类别数量是否均衡,再传上去。
3.3 数据读取的性能优化
数据在 OBS 上,训练时读取会有网络延迟。ModelArts 提供了几种优化手段:
- 使用缓存:训练作业可以配置数据缓存,第一次读取后缓存在本地,后续 epoch 读取就快了。
- 数据格式转换:把大量小图片转成 TFRecord 或者 MindRecord 格式,减少小文件读取的开销。我两万张图,转成 Record 格式后,每个 epoch 的数据加载时间从几分钟降到了几十秒。
- 合理设置 num_workers:DataLoader 的
num_workers设成 CPU 核数的 2-4 倍比较合适,设太大反而会因为进程切换开销变慢。
这里有个细节:num_workers在 Notebook 里调试时建议设成 0,方便看报错堆栈;正式训练时再调大。因为多进程下报错信息会被吞掉,排查起来很痛苦。
4. 训练脚本编写与 Notebook 调试要点
4.1 从 Notebook 到训练脚本的转换
Notebook 适合交互式调试,但正式训练要提交作业,得把代码整理成脚本。我的做法是:在 Notebook 里把数据加载、模型定义、训练循环都调通,然后把代码拆成几个文件:
train.py:主入口,解析参数、启动训练model.py:模型定义dataset.py:数据加载utils.py:工具函数
拆文件的好处是结构清晰,而且训练作业提交时可以指定入口文件,平台会把你指定的代码目录整体打包上传。
参数解析用argparse,把学习率、batch size、epoch 数、数据路径这些都做成命令行参数。这样同一个脚本可以用不同参数跑多次实验,不用改代码。
import argparse parser = argparse.ArgumentParser() parser.add_argument('--data_url', type=str, default='./dataset') parser.add_argument('--train_url', type=str, default='./output') parser.add_argument('--epochs', type=int, default=50) parser.add_argument('--batch_size', type=int, default=32) parser.add_argument('--lr', type=float, default=0.001) args = parser.parse_args()注意data_url和train_url这两个参数名,ModelArts 在训练作业里会自动注入这两个参数,分别指向你的数据存储路径和输出路径。你只要在脚本里接收就行,不用自己拼路径。
4.2 预训练模型的加载与微调
我的任务数据量不算特别大,从头训练容易过拟合,所以用了 ResNet50 的预训练权重做微调。加载预训练模型有两种方式:
import torchvision.models as models import torch.nn as nn # 方式一:直接加载,替换最后一层 model = models.resnet50(pretrained=True) num_features = model.fc.in_features model.fc = nn.Linear(num_features, num_classes) # 方式二:手动加载权重文件 model = models.resnet50(pretrained=False) state_dict = torch.load('resnet50-weights.pth') model.load_state_dict(state_dict, strict=False)方式一需要联网下载权重,ModelArts 的实例一般能访问外网,但有时候会慢。方式二是提前把权重文件传到 OBS,训练时从本地读,更稳定。我后来都改用方式二,把常用的预训练权重提前传好。
微调的时候,我一般冻结前面的层,只训练最后的全连接层和最后几个 block。这样训练快,也不容易过拟合。具体冻结哪些层,取决于你的数据量和任务相似度。数据量小、任务和预训练任务差异大,就多训练几层;反之就少训练几层。
4.3 训练过程中的监控与日志
训练作业提交后,你可以在控制台看日志。但光看日志不够直观,我习惯在代码里加 TensorBoard 或者 MindInsight 的记录,把 loss、accuracy、学习率这些曲线画出来。
from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter(args.train_url + '/logs') # 训练循环里 writer.add_scalar('Loss/train', loss.item(), global_step) writer.add_scalar('Accuracy/train', acc, global_step)日志目录要写到train_url下面,这样训练结束后能在 OBS 里找到。ModelArts 也支持把 TensorBoard 日志直接可视化,在训练作业详情页有入口。
实操心得:训练作业的日志有延迟,不是实时的。我一般提交后先去干别的,过十几分钟再回来看。如果发现 loss 一直是 nan,那多半是学习率太大或者数据有问题,赶紧停了改,别让它白跑。
5. 模型部署上线的完整流程
5.1 模型产出的保存与注册
训练完成后,模型文件会保存在train_url指定的路径下。我一般保存两个东西:模型权重文件和推理代码。权重文件就是state_dict,推理代码是加载模型、做前处理的脚本。
ModelArts 的模型管理需要你指定模型文件的路径和推理代码的入口。推理代码要遵循平台的规范,核心是实现一个Model类,里面有__init__和_inference方法:
class Model: def __init__(self, model_path): self.model = load_model(model_path) self.model.eval() def _inference(self, data): # 前处理 input_tensor = preprocess(data) # 推理 with torch.no_grad(): output = self.model(input_tensor) # 后处理 return postprocess(output)这个类名和方法名是平台约定的,不能随便改。前处理要和你训练时的处理保持一致,否则推理结果会不对。我就在这里踩过坑:训练时用了归一化,推理时忘了,结果预测全乱。
5.2 在线服务的配置与发布
模型注册好后,就可以创建在线服务了。配置项主要有几个:
- 算力规格:推理一般比训练要求低,选个中等规格就行。我的服务用的是 CPU 规格,因为推理请求量不大,用 GPU 浪费。
- 实例数量:可以设多个实例做负载均衡,我一开始设了 1 个,后来请求量上来了加到 2 个。
- 环境变量:如果有配置项,通过环境变量传进去。
发布后,平台会给你一个 API 地址和认证信息。调用的时候需要在请求头里带上 token,token 的获取方式在服务详情页有说明。
import requests url = "https://your-service-endpoint/v1/infers/xxx" headers = { "X-Auth-Token": "your-token", "Content-Type": "application/json" } data = {"data": {"image": "base64编码的图片"}} response = requests.post(url, headers=headers, json=data) print(response.json())5.3 部署后的性能与成本优化
服务跑起来后,我做了几件事来优化:
第一,批处理。如果请求量大,可以把多个请求攒成一批一起推理,吞吐量能提升好几倍。但这会增加单次延迟,要权衡。
第二,模型量化。把 FP32 的模型转成 FP16 或者 INT8,推理速度能提升,模型体积也变小。我用 FP16 量化后,推理延迟降了大概 30%,精度几乎没损失。
第三,自动扩缩容。ModelArts 支持根据请求量自动调整实例数,低峰期缩到 1 个,高峰期自动扩。这个能省不少钱,但要配置好触发阈值。
注意:在线服务的计费是按实例运行时长算的,哪怕没有请求也在计费。如果服务不是 7x24 需要的,可以设置定时启停,或者用按需的批量服务。
6. 常见报错与排查技巧实录
6.1 训练阶段的典型问题
问题一:loss 变成 nan。这是最常见的。原因通常是学习率太大、数据里有异常值、或者梯度爆炸。我的排查顺序是:先把学习率降一个数量级试试;然后检查数据里有没有空文件或者损坏的图片;最后加梯度裁剪。
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)问题二:显存不够(CUDA out of memory)。解决办法有几个:减小 batch size、用梯度累积模拟大 batch、用混合精度训练、清理不必要的缓存。
# 混合精度训练 from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() with autocast(): output = model(input) loss = criterion(output, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()问题三:数据读取报错。多半是路径问题。ModelArts 里数据路径是平台注入的,不要写死。用args.data_url拼接。另外注意 OBS 路径和本地路径的区别,OBS 路径是obs://开头,本地是/cache/开头。
6.2 部署阶段的典型问题
问题一:推理结果和训练时不一致。九成是前处理不一致。检查归一化的均值方差、图片的 resize 方式、通道顺序(RGB vs BGR)是否和训练时一样。
问题二:服务启动失败。看日志,通常是推理代码有语法错误,或者依赖没装。ModelArts 的推理环境是独立的,你训练时用的包,推理环境里不一定有。需要在推理代码目录里放一个requirements.txt,平台会自动安装。
问题三:调用超时。默认超时时间可能比较短,如果你的模型推理慢,需要在创建服务时调大超时时间。另外检查网络,服务地址是内网还是公网,公网调用要注意带宽。
6.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决办法 |
|---|---|---|---|
| loss 为 nan | 学习率过大/数据异常 | 打印每步 loss 和输入 | 降学习率、清洗数据、梯度裁剪 |
| 显存不足 | batch 太大/模型太大 | 看显存占用 | 减小 batch、混合精度、梯度累积 |
| 数据读取慢 | 小文件多/跨区域 | 看数据加载耗时 | 转 Record 格式、同区域建桶 |
| 推理结果不对 | 前处理不一致 | 对比训练和推理代码 | 统一归一化和 resize 方式 |
| 服务启动失败 | 依赖缺失/代码错误 | 看服务日志 | 补 requirements.txt、修代码 |
| 调用超时 | 推理慢/超时设置短 | 测单次推理耗时 | 优化模型、调大超时 |
6.4 几个文档里不会写的避坑经验
第一,Notebook 用完就关。前面提过,但真的重要。我见过有人挂着 Notebook 跑了一周,账单出来吓一跳。
第二,训练作业的日志要主动保存。作业结束后,实例释放,日志可能就找不到了。我习惯在代码里把关键日志写到train_url下,这样作业结束后还能在 OBS 里翻。
第三,模型文件命名要规范。我一开始用model.pth,后来模型多了根本分不清哪个是哪个。改成resnet50_bs32_lr0.001_epoch50.pth这种带参数的命名,一目了然。
第四,先跑通再优化。不要一上来就追求多卡、混合精度、数据增强全套。先用最简单的配置跑通一个 epoch,确认链路没问题,再逐步加优化。这样出问题的时候,你知道是哪个改动引入的。
第五,善用免费额度。华为云对新用户有一些免费算力额度,ModelArts 也有免费体验的规格。学习阶段先用免费的,跑通了再上付费规格。
7. 关于 vLLM 与大模型部署的一点延伸
虽然我这个项目是图像分类,但最近团队也在看大模型的部署,顺便研究了一下 vLLM。这里简单说几句,因为热词里也提到了。
vLLM 是一个专门做大模型推理加速的框架,核心是 PagedAttention 技术,能显著提升吞吐量。如果你要在 ModelArts 上部署类似 DeepSeek、Qwen 这类大模型,vLLM 是个不错的选择。部署思路和上面类似:把模型权重传到 OBS,写推理代码,创建在线服务。区别在于 vLLM 对显存要求高,要选大显存的规格,而且它的推理代码有自己的 API 规范。
有个坑要注意:安装 vLLM 可能会改变你已经装好的 torch 版本。因为 vLLM 对 torch 版本有依赖,pip 安装时可能会升级或降级 torch,导致你原来的环境坏掉。我的建议是用独立的虚拟环境,或者直接用 ModelArts 的预置镜像,里面可能已经集成了。
至于本地部署 AI 模型,那是另一条路,对硬件要求高,适合有显卡的开发者折腾。云上部署的优势是弹性,用多少算多少,不用一次性投入硬件成本。
8. 写在最后的一点个人体会
这套流程跑下来,我最大的感受是:云平台的价值不在于技术多先进,而在于把繁琐的工程问题标准化了。以前我要花大量时间在环境配置、算力调度、日志收集上,现在这些平台都帮你做了,你可以把精力集中在模型和数据本身。
当然,平台也有它的脾气。文档有时候跟不上版本迭代,报错信息有时候不够明确,这些都需要你自己去踩、去试。但踩过一遍之后,你会发现这套东西是可复用的——下次换个任务,换个模型,流程还是这个流程,只是参数变一变。
如果你刚开始用 ModelArts,我的建议是:别怕报错,别怕花钱,先用最小的成本把整个链路跑通一遍。跑通之后,你对整个系统的理解会完全不一样。那些文档里看不懂的概念,在实操中自然就明白了。