上半年我把一个图像分类项目和一个文本分类项目都放上了华为云 ModelArts,从数据准备、模型训练到在线部署完整走了一遍。这篇笔记不是官方文档的复述,而是我自己实际操作中的完整记录,包括为什么这么选、每一步怎么做、踩了哪些坑、最后怎么排查。如果你正准备用 ModelArts 做模型训练和部署,或者正在纠结到底是自己租 GPU 服务器还是用云平台,这篇文章应该能帮你省下不少时间。
ModelArts 是华为云上的一站式 AI 开发平台,覆盖了数据标注、训练、推理部署全流程。我个人的理解是:它把过去本地炼丹那套东西——装驱动、配环境、写训练脚本、做服务化——全部打包成了云上的托管服务。你只需要关心模型本身,基础设施的事情交给平台。这篇笔记适合以下几类人:刚接触 AI 工程化、被本地环境折腾到崩溃的初学者;想在短时间内验证一个模型效果的开发者;以及团队里需要把模型快速变成 API 给业务调用,但又不想自己运维 GPU 集群的中小团队。
1. 为什么选 ModelArts 而不是自己从头搭一整套
1.1 本地训练的那些真实痛点
先说我自己之前的经历。一台普通台式机,装了一块消费级显卡,显存 8GB。跑一个中等规模的数据集,batch size 稍微大一点就直接 out of memory。换了小的 batch size,训练速度又慢得让人怀疑人生。比这更折磨的是环境配置:CUDA 版本要跟显卡驱动匹配,PyTorch 跟 CUDA 要匹配,不同项目的依赖互相冲突是家常便饭。每次重装完系统,光是把环境恢复过来就要折腾一整天。
还有部署。本地训练完的模型,要给别人用,就得自己写 Flask 服务,配 uwsgi 和 nginx,处理跨域、鉴权、并发。模型文件几个 G,服务一重启就要重新加载。用了几个星期,发现问题基本不是出在模型本身,而是出在环境和服务化上。这种情况我相信不少人都遇到过。
后来我把目光转向云平台,对比了几家,最后在 ModelArts 上做了完整项目。因为它的核心逻辑特别对我胃口:训练就是提交一个任务,部署就是把模型发布成一个服务,全程不用碰服务器。
1.2 ModelArts 替我省掉的几类事情
ModelArts 对我这种「想专注模型」的人来说,真正解决的问题可以拆成三块:
第一,算力按需使用。本地想用 V100 级别的显卡基本不可能,但在 ModelArts 上可以选择不同规格的 GPU 资源。训练任务跑完就可以释放,不用像买了一台服务器那样 7x24 小时花钱。这意味着我可以在需要大算力的时候临时租用,平时用 CPU 或小规格 GPU 做开发和调试。
第二,环境免配置。平台提供预置镜像,常见的 AI 框架如 PyTorch、TensorFlow、MindSpore 都已经装好,版本固定且互相适配。我一般直接选 PyTorch 的预置镜像,打开就是一个能跑通的环境,不再需要自己折腾 CUDA 和 cuDNN。
第三,训练和部署无缝衔接。训练任务产生的模型产物会自动存到对象存储,部署的时候直接引用,中间不需要人工搬运文件。ModelArts 还支持把训练任务一键部署成在线服务,API 直接可用。
1.3 哪些场景下我会劝你别急着用
ModelArts 不是万能的。如果你属于下面这几种情况,可能需要再想想:
- 数据量极大且训练非常频繁,比如每天跑十几个任务持续几个月。这种场景下,长期租用固定 GPU 服务器可能比按任务付费划算,你需要仔细做成本测算。
- 算法非常特殊,依赖特定的操作系统内核特性或第三方库,标准预置镜像支持不了,自定义镜像的维护成本又太高。
- 数据有强合规要求,完全不能出内网。这种情况私有化部署是硬性要求,云平台无法满足。
不过对大多数学习、实验和常规业务场景,ModelArts 的设计思路都是适用的。
2. 训练前的关键准备:数据、代码和资源规划
2.1 把数据放到 OBS:并行文件系统的选择
ModelArts 本身不直接存数据,它训练时是从 OBS(对象存储服务)读取数据的。所以第一步是创建 OBS 桶并准备数据。
我在做数据准备时踩过一个坑:一开始创建的是普通的 OBS 桶,后来发现 ModelArts 的训练任务读取性能一般。后来查资料才知道,ModelArts 官方更推荐使用并行文件系统(PFS),它的读写性能更好,尤其在训练过程中频繁读取小文件时会明显更快。建议你创建的时候直接选「并行文件系统」。
数据目录的组织也很重要。我把数据集和输出结果分开存放,避免混淆。一个典型的目录结构是这样的:
obs://my-bucket/ ├── data/ │ ├── train/ │ │ ├── class1/ │ │ └── class2/ │ └── val/ │ ├── class1/ │ └── class2/ ├── output/ └── model/这里以图像分类为例,train 和 val 下面是按类别分的子目录。上传数据可以使用华为云提供的 obsutil 命令行工具,也可以直接在控制台上传。数据量大时用 obsutil 更稳,支持断点续传,比网页上传快得多。
注意:OBS 的路径格式是
obs://bucket-name/object-key,这在 ModelArts 后续的配置中会频繁用到,建议先熟悉一下。
2.2 Notebook 环境的使用技巧
ModelArts 的 Notebook 是我用来做数据探索和小规模试验的地方。它的优势在于:预置镜像、GPU 资源可以随时切换、环境持久化。
选规格的时候,我建议按需求来。只是做数据处理和看数据分布,用 CPU 规格就够,比如 8 核 32GB;真正开始训练模型,再切换到 GPU 规格。切换的代价是重新等待环境启动,所以我会尽量把数据处理和训练脚本调试分开:先在 CPU 实例上搞定数据处理,再启动 GPU 实例跑训练。
Notebook 还内置了代码自动保存的机制,文件会保存在系统盘的某个目录中。我通常会把代码放进/home/ma-user/work/目录,因为这是系统自动挂载的存储路径,实例释放后代码还在,而系统盘在其他目录的改动可能会丢失。
2.3 预训练模型的选择:ResNet 与 RoBERTa 的引入方式
图像任务,我用了 ResNet 系列作为骨干网络。文本任务,我研究了一下 RoBERTa 中文预训练模型。这里有一个通用原则:预训练模型的参数必须与任务的输入输出结构匹配。
以图像分类为例,加载预训练模型时要把最后一层全连接替换成新的分类层。代码大概是这样的:
import torchvision.models as models model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) num_ftrs = model.fc.in_features model.fc = torch.nn.Linear(num_ftrs, num_classes)对于中文文本分类,RoBERTa 模型我建议先从开源社区下载原始的 PyTorch 权重文件,上传到 OBS,再通过代码加载。这里有个容易出问题的地方:预训练模型的词表(vocab.txt)与训练脚本里 tokenizer 的版本必须一致,否则解码就会乱码,准确率直接崩掉。
3. 模型训练的核心实操
3.1 创建训练任务:订阅算法 vs 自定义脚本
ModelArts 的「训练管理」里有两种常见方式:一是使用内置算法或市场订阅的算法,二是上传自己的训练脚本。
内置算法适合跑通流程,比如系统里有一些现成的图像分类算法,你只需要指定数据路径就能跑。但我自己的项目基本都是自定义脚本,因为内置算法的灵活度不够。自定义脚本的规范并不复杂,核心是写好入口和参数解析。
训练脚本的入口通常是这样的结构:
import argparse import os parser = argparse.ArgumentParser() parser.add_argument('--data_url', type=str, help='训练数据所在的OBS路径') parser.add_argument('--train_url', type=str, help='训练输出目录') parser.add_argument('--epochs', type=int, default=30) parser.add_argument('--batch_size', type=int, default=32) parser.add_argument('--learning_rate', type=float, default=1e-4) args = parser.parse_args() # 训练逻辑 ... # 保存模型 torch.save(model.state_dict(), os.path.join(args.train_url, 'model.pth'))关键点在于data_url和train_url这两个参数。创建训练任务时填写的路径会通过环境变量传给脚本。很多初学者以为代码里直接写死路径就行,但这会导致换数据集或换输出路径时又得改代码重传,非常低效。
3.2 参数配置与资源选择:batch size 和显存的账要算清楚
在 ModelArts 上创建训练任务时,核心配置项包括:AI 框架、训练规格、最大训练时长、超参数。
训练规格的选择直接影响速度和成本。以 GPU 为例,常见的有以下规格:
| 规格类型 | GPU 型号 | 显存 | 适用场景 |
|---|---|---|---|
| 基础型 | 某些入门级 GPU | 16GB 左右 | 轻量模型或小 batch |
| 进阶型 | V100 | 16GB / 32GB | 中型模型 |
| 高性能型 | A100 | 40GB / 80GB | 大模型、大 batch |
我训练 ResNet50 时选了 16GB 显存的规格。那你可能想问,怎么估算 batch size 能不能装下?这里有一个近似公式可以帮你提前判断:
总显存占用 ≈ 模型参数量 × 每参数字节数 + batch_size × 单样本前向/反向临时显存
以 ResNet50 为例,参数量约 2560 万,单精度浮点下每个参数占 4 字节,模型本身约占用 100MB。但训练时的临时激活值才是大头,这个值跟输入分辨率、batch size 强相关。实际操作中,我建议你写一个小的摸底脚本,用不同 batch size 尝试前向和反向传播,观察显存占用。我用一个 batch size 从 32 开始的递进测试,发现 64 会出现 OOM,最终稳定用的是 32。
超参数这一块,我自己常用的基线配置大概是:
--epochs 50 --batch_size 32 --learning_rate 1e-4学习率这块要重点说。预训练模型微调时,学习率不宜太大。我从1e-4起步,配和线性 warmup,前 5 个 epoch 从 0 慢慢升到设定值,避免一开始就大步更新破坏预训练权重。这是实践中非常稳的方案配置。
注意:ModelArts 训练任务运行时,输出结果默认写到临时目录,任务结束后会自动同步到 OBS。所以在脚本中保存模型时,一定要使用平台提供的
train_url路径,而不是本地写死的路径。我见过有人把模型保存到了/home/ma-user下面,结果任务结束模型直接不见。
3.3 日志监控与产物管理
训练任务跑起来之后,怎么看进度?ModelArts 控制台的训练页面能实时查看日志输出,但这只是基本操作。我的习惯是:在脚本里把关键指标用print打出来,同时周期性地把 loss 和 accuracy 记录到日志文件中。
对于训练过程的可视化,ModelArts 提供了一个更高级的玩法:在脚本里集成 MindInsight 或 TensorBoard。不过我自己更偏向在脚本里每隔一段时间就评估一次验证集,把结果打印出来。这个方法朴实但有效,只要日志里能看到 val_acc 在稳定提升,心里就踏实。
训练结束后,模型产物会存放在指定的train_url下。ModelArts 会自动把输出上传到 OBS。我一般会额外输出一个config.json,把训练参数、标签映射、预处理方式都记上去。这个文件在后续部署时极其有用,因为部署的推理代码需要知道输入尺寸和类别数。
3.4 训练过程中遇到 loss 变成 NaN 的经验
热词里频繁出现「模型训练报 nan」,这问题我在 ModelArts 上也遇到过。一次微调 RoBERTa 分类模型时,loss 在前几百步还正常,某个 step 突然变成 NaN,之后再也回不来。
排查思路可以按照下面这个顺序走:
第一,查学习率。如果学习率太大,模型参数会在梯度更新时溢出。我那次设定的学习率是5e-5,按 RoBERTa 微调的经验应该算温和,但我同时开了混合精度训练,问题更可能出在精度上。
第二,查混合精度相关设置。混合精度会用 FP16 参与计算,梯度下溢的概率会增加。我的解决办法是给损失缩放加上动态调整机制,或者干脆先关掉混合精度。如果你不确定自己的代码是否支持 FP16 的稳定性,建议直接关闭。
第三,查数据本身。输入中如果含有 NaN 或异常值,会导致 loss 计算失败。我在数据预处理里加了检查,保证输入张量无 NaN。
第四,查优化器的梯度裁剪。给梯度加一个最大范数限制(常见 1.0),可以防止梯度爆炸带来的数值溢出。
那次最终的解决方案是:关闭混合精度 + 降低学习率到2e-5+ 梯度裁剪。重新跑就正常了。所以遇到 NaN,先不要慌,按顺序排查。
4. 模型部署成在线服务
4.1 部署方式的选型:训练产出直部署 vs 模型导入
训练完成之后,接下来是部署。ModelArts 提供两种常见路径:
第一种路径是训练任务里直接选择「部署」,好处是模型产物已经关联好了,一键就能创建在线服务。第二种路径是先在模型管理里「导入模型」,再基于这个模型部署在线服务。
如果你只是实验验证,第一种够用。但如果你想把同一个模型部署多个版本,或者在多个训练任务之间做对比,建议用第二种。「模型管理」更像一个模型仓库,允许你上传不同版本的模型文件,统一管理后再部署,生产环境会从容很多。
4.2 模型包规范和推理代码 customize_service.py
模型导入到 ModelArts 时,需要遵循它定义的模型包规范。一个标准的模型包长这样:
model/ ├── model.pth # 模型权重文件 ├── config.json # 模型描述与参数 ├── customize_service.py # 推理脚本 └── 其他依赖文件customize_service.py是指定推理逻辑的核心文件。它定义了一个类,继承平台的基类,实现preprocess和postprocess。一个极简的例子:
from model_service.model_service import ModelService import torch import torchvision.transforms as transforms from PIL import Image class MyModelService(ModelService): def __init__(self, model_path, model_name): super().__init__(model_path, model_name) self.model = torch.load(model_path + '/model.pth', map_location='cpu') self.model.eval() self.transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def preprocess(self, data): # data 是平台解析后的请求体 image = Image.open(data['image_path']) return self.transform(image).unsqueeze(0) def postprocess(self, infer_outputs): probabilities = torch.softmax(infer_outputs, dim=1) _, pred = torch.max(probabilities, 1) return {'predicted_class': int(pred)}这段代码中有几个细节很重要。第一,模型是加载到 CPU 上推理的,因为部署服务时的 GPU 资源是按需分配,CPU 推理对大多数业务场景足够。第二,预处理必须与训练时完全一致,我在这里用到了前面提到的 config.json 里记录的尺寸和均值方差。第三,preprocess的入参格式需要注意,实际部署时平台会根据请求的类型做封装,建议先跑通默认写法再微调。
注意:模型包内的依赖如果包含平台预置镜像中没有的库,就需要在模型包中带上
requirements.txt,平台会在部署时执行安装。但要谨慎,安装依赖会增加部署时间,且可能失败。能用平台镜像自带库解决的,就尽量不要额外依赖。
4.3 在线服务的调用与并发配置
部署完成后,ModelArts 会给每个在线服务分配一个 API 地址。调用模型服务就是给这个地址发 HTTP POST 请求。
一个最简单的调用方式:
curl -X POST https://your-endpoint-url \ -H "Content-Type: application/json" \ -d '{"image_path": "obs://bucket/test.jpg"}'实际生产调用时,通常会发现单实例并发不够。ModelArts 在线服务支持设置实例数,还可以配置自动扩容策略。我的建议是:先压测,再配置实例数。压测方法很简单,用脚本模拟并发请求,观察响应延迟和成功率。不要一上来就开 10 个实例烧钱,很多时候单实例配合合理的超时配置已经够用。
4.4 我部署图像分类模型的一次完整记录
这里放一个我实际操作的流程,方便你照抄。
第一步,确认模型文件的格式。我用 PyTorch 保存的是 state_dict 文件,因此在加载时要先构造模型结构再 load。如果你保存的是整模型,加载方式又不一样。
第二步,整理模型包。我把权重文件、config.json、customize_service.py 打成了一个压缩包,上传到 OBS。
第三步,在 ModelArts 的「模型管理」里导入模型。填写元信息时,选择「从 OBS 导入」,指定模型包在 OBS 的路径。
第四步,创建在线服务。选择刚才导入的模型,推理资源配置选 CPU 规格,实例数先设为 1,填写服务名称。
第五步,等待部署。这个过程一般需要几分钟,主要花在拉取镜像和加载模型上。部署成功后,拿到 API 地址,先发一个小测试请求确认返回值,然后逐步加并发压测。
整个流程跑下来,从模型文件到可用 API,大概 20 分钟。这个效率是明显优于自己搭服务器的。
5. 常见问题与避坑速查表
5.1 高频问题的排查与解决
我把这段时间使用 ModelArts 遇到的问题整理成了一个表格,按出现频率排序:
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| Notebook 启动后很慢 | 选择的规格资源不足 | 换更高配置的规格,或检查是否同时启动了多个实例 |
| 训练任务数据读取慢 | 使用了普通 OBS 桶而非并行文件系统 | 新建并行文件系统,迁移数据集 |
| 训练时 loss 为 NaN | 学习率过大、混合精度不稳定、数据含 NaN | 降低学习率、关闭混合精度或做梯度裁剪、清洗数据 |
| 部署服务时模型加载失败 | 模型包路径错误或模型文件不匹配 | 检查 OBS 路径、模型结构是否与权重匹配 |
| 在线服务超时 | 实例规格小,推理耗时过长 | 缩短预处理耗时、换更高规格、增加实例并发 |
| 模型输出乱码(文本任务) | tokenizer 词表与预训练模型不匹配 | 使用与预训练模型配套的 vocab 和 config |
| 训练任务结束后模型文件不存在 | 保存到了系统盘而非 train_url 指定路径 | 修改脚本,确保模型保存在平台指定的输出目录 |
5.2 成本控制的小技巧
ModelArts 的计费主要分三块:存储、计算和推理服务时长。我实际用下来,有几点经验:
第一,Notebook 在不使用的空闲时间要主动停止。Notebook 即使不跑代码,只要实例存活就在扣费。官方有自动停止功能,可以设置空闲多少分钟自动停止,强烈建议开启。
第二,训练任务尽量把「最大运行时长」设短。平台支持设置最大训练时长,超时自动停止。这个配置既能防止死循环烧钱,也能提醒你关注训练进度的合理性。
第三,在线服务如果只是白天有人调用,可以考虑用「定时启停」的能力。虽然 ModelArts 不直接提供实例定时启停,但可以通过脚本调用 API 实现,或者干脆部署到非高峰期再启动。
第四,免费额度的使用要精打细算。平台有时会有新用户免费额度,我建议把它用在最关键的大规格 GPU 训练上,而不是日常小任务。
5.3 从学习项目到生产落地的几个扩展方向
这篇笔记到这里,其实已经把 ModelArts 的训练和部署链路走通了。如果你跟我一样是从学习笔记起步,后续想要落地生产,我会建议从这几个方向继续深入:
把模型服务接入统一网关。ModelArts 提供的在线服务 API 可以直接暴露给业务方,但为了统一鉴权、限流、监控,建议在前面叠加一层网关。
增加模型的版本管理。每次重新训练,都应该给模型打版本标签,并在模型管理中保留历史版本。发布新版本时用灰度发布,先让一小部分流量走新模型,确认指标不劣化再全量切换。
监控与告警。模型服务上线不是终点,推理的延迟分布、请求失败率、输入数据分布漂移都是需要长期观察的指标。ModelArts 的日志服务对接了云日志服务,建议把推理日志都结构化打印出来,后续分析会顺畅很多。
我在实际操作中最深的体会是:ModelArts 的定位不是让你做一个炼丹师,而是让你把精力放回模型本身。环境问题、资源问题、服务化问题,它替你兜底了。但你依然需要理解训练脚本里的每个超参数、数据预处理的每个细节,这些模型能力的基础,没有任何平台能替你完成。把这套链路走通之后,你的模型从笔记本里的一堆权重变成线上一个个真实的推理请求,这个转变本身就是 AI 工程化里最有价值的一步。