一个多月前,我给自己定了个小目标:把手里的一个图像分类需求真正跑通,从数据集准备到模型上线,全流程走一遍。以前训练模型基本靠本地显卡硬扛,显存不够就缩batch size,再不够就换轻量网络,一直想试试云端的训练推理一条龙。朋友推荐了华为云ModelArts,趁着有免费算力额度,我把“基于华为云ModelArts完成模型训练与部署”这件事完整实践了一遍。这篇笔记就是这段时间踩坑、查文档、反复调试后的记录,希望能给同样打算用ModelArts做训练部署的朋友一些参考。
我这次的目标很明确:用ModelArts训练一个图像分类模型,然后把模型部署成在线服务,能够在公网调用API拿到预测结果。整套流程下来,我发现ModelArts真正解决的痛点是“不用自己管理GPU驱动、不用买卡、不用折腾Docker和推理框架”,有浏览器就能完成从数据到服务的大部分工作。对于学生、个人开发者以及想快速验证想法的小团队来说,这确实是条很省心的路径。
这篇笔记适合几类人看:完全没用过华为云、但想尝试云端训练的新手;已经跑通本地训练、想知道ModelArts和自建环境的差异的进阶用户;以及准备做模型部署、但不想从头搭建推理服务的人。我会把账号准备、数据上传、训练配置、模型部署这些环节的关键操作和原理写清楚,也会把我在实际过程中踩过的坑原原本本列出来。
1. 整体思路拆解:为什么选择ModelArts,而不是裸租GPU云主机
1.1 ModelArts和传统“租服务器自己装环境”的本质区别
很多人第一次接触云端训练,第一反应是去租一台带GPU的云主机,然后像用本地机器一样装CUDA、装驱动、装PyTorch。这条路不是不行,但有几个很现实的问题:GPU云主机按时长计费,环境装好可能就花了半天;机器学习依赖版本耦合严重,PyTorch、CUDA、cuDNN之间差一个版本都可能跑不起来;训练完了还要自己部署推理服务,又得处理Flask、Nginx这些东西。
ModelArts的思路完全不同,它把“训练”和“部署”做成托管服务。你不用关心底层驱动和框架版本,只要选一个合适的“规格”,告诉平台用哪个算法、哪个数据集、多少算力,它就在后台把训练任务跑起来。训练完成后,模型会自动注册到模型列表里,之后可以直接一键部署成在线服务。这个模式很像点外卖:你不用自己买菜、洗菜、炒菜,只需要看着菜单下单。当然,代价就是你只能在平台提供的菜单里选菜,自由度比自建环境低一些,但换来的效率提升非常可观。
从成本角度看,也值得算一笔账。我本地显卡是8G显存,跑ResNet50这种模型batch size只能开到32左右,一张A100按小时租又太贵。ModelArts的免费额度用来学习和验证完全够用;如果正式训练,按需使用、用完即停,比长期租用GPU主机划算很多。尤其是训练任务本身是间歇性的,不用的时候不花钱,这点对个人开发者相当友好。
1.2 本次实践的整体流程和各环节关系
整个流程可以拆成五个环节:账号准备、数据准备、训练配置、模型管理、在线部署。这五个环节在ModelArts控制台里是五个独立的页面,但逻辑上是一条流水线:数据从OBS进入训练容器,训练产物输出到OBS指定目录,之后再从OBS注册成模型版本,部署时再从模型版本拉起推理实例。
一开始我没有把这五个环节当成整体理解,走了些弯路。比如训练时数据集路径写错,任务直接失败;又比如训练结束后以为模型会自动出现在模型列表里,其实还要手工“导入模型”。所以我会在每一部分都强调“这个环节的输出是下一个环节的输入”,明白了数据流向,操作时就不容易漏步骤。
下面这张表是我在实践后整理的环节速查,后面每一节都会展开细说:
| 环节 | 核心动作 | 主要产物 | 常见误区 |
|---|---|---|---|
| 账号准备 | 注册账号、实名认证、开通ModelArts、创建OBS桶 | 华为云账号、OBS桶 | 没开OBS就上传数据,上传无目标 |
| 数据准备 | 数据清洗、按类别组织文件、上传OBS | 训练集/验证集目录 | 目录结构不规范,训练脚本读取失败 |
| 训练配置 | 创建训练作业,指定算法、数据路径、资源规格 | 模型权重文件、日志 | 输出路径写错,找不到模型文件 |
| 模型管理 | 导入模型到ModelArts模型列表 | 模型版本 | 忘了加推理配置文件,无法部署 |
| 在线部署 | 部署为在线服务,获取API地址 | API地址、调用凭证 | 安全组/访问权限没配置,外部无法调用 |
1.3 我踩过的“配置误区”:一开始误以为ModelArts只是一个IDE
刚开始用的时候,我误以为ModelArts主要就是个云端Notebook,打开以后在Jupyter里写代码。确实,平台提供了Notebook功能,可以创建开发环境,但我这次的目标是跑完整的训练和部署,如果只停留在Notebook里敲代码,那和本地开发差不多,体现不出托管服务的价值。
真正把ModelArts用起来,应该让训练作业变成“声明式”的:你在创建训练作业时指明算法代码的位置、数据集的位置、输出位置、资源规格,然后平台按这个描述去执行。这种方式下的训练代码最好是独立可运行的,不要依赖交互式环境。我在实践时专门把训练脚本写成了从命令行接收参数的形式,这样既可以本地调试,也可以无缝提交到云端。
理解了这套设计逻辑,后面的操作基本就是顺着平台的意愿走:数据放OBS、代码打包上传、训练作业指向这些位置、等待日志输出、获取模型产物。每一步都有对应的控制台入口和API,多花点时间理解整体架构,操作起来会很顺畅。
2. 准备工作与数据工程:OBS桶、数据集和训练脚本的组织方式
2.1 创建OBS桶和数据目录结构规划
ModelArts本身不存数据,所有数据都放在对象存储服务(OBS)里。你可以把OBS理解成一个网盘,但它支持海量并发访问,也支持从训练容器里直接读取。创建OBS桶这一步,很多教程都是一笔带过,但我实际操作时发现有几个细节直接影响后续使用:首先,OBS桶要和你使用ModelArts的区域保持一致,比如都在“华北-北京四”,否则跨区域访问会有额外延迟和流量费用;其次,桶的权限建议用“私有”,每次训练时通过委托授权给ModelArts访问,而不是把桶设成公开读,后者有数据泄露风险。
数据目录结构我也建议在首次上传前就想清楚。我这次用的数据是猫狗图片分类,目录结构是这样组织的:
bucket-name/ ├── data/ │ ├── train/ │ │ ├── cat/ │ │ │ ├── cat_001.jpg │ │ │ └── ... │ │ └── dog/ │ │ └── ... │ └── validation/ │ ├── cat/ │ └── dog/ ├── code/ │ └── train.py ├── output/ └── model/这个结构看起来简单,但意义不小:训练脚本可以统一用--data_url指向data/目录,--train_url指向output/目录,不用在代码里写死绝对路径。很多开源训练脚本习惯直接把数据放在当前目录,这在本地没问题,但在云端训练时,工作目录是临时的,数据必须从OBS获取,所以把数据路径作为参数传入是最稳妥的做法。
2.2 数据上传的三条路径:Console上传、OBS Browser+、命令行工具
数据上传方式,我试了两种,觉得最值得推荐的是命令行工具。控制台直接上传适合小文件,比如几十张图片的测试数据;但一旦数据到了几百MB甚至几GB,浏览器上传的体验就很痛苦,容易中断且没有断点续传。OBS Browser+是图形化客户端,支持文件夹批量上传和断点续传,适合一次性的数据迁移。
我这次的数据集大约1.2GB,用的是OBS命令行工具obsutil。上传之前先下载并配置好访问密钥,然后一条命令传整个目录:
# 配置obsutil访问密钥(只需要执行一次) ./obsutil config -i=AK -k=SK -e=https://obs.myhuaweicloud.com # 把本地data目录同步到OBS桶 ./obsutil cp ./data obs://bucket-name/data -r -f # 查看桶内文件,确认传上去了 ./obsutil ls obs://bucket-name/dataobsutil支持大文件分段上传和并发传输,速度比我预期快很多,1.2GB的数据大概几分钟就传完了。传完后一定要做一次检查,我当时就是漏了这一步,训练作业找不到验证集目录,排查半天才发现是上传时目录路径少了一层。用obsutil ls列一下目录结构,虽然麻烦点,但能提前暴露很多低级问题。
还有一个经验:数据很小时,可以直接从Notebook环境拉取公共数据集,ModelArts的公共数据集列表里有一些现成的数据,比如MNIST、CIFAR-10。但从公共数据集拉取的数据通常不带OBS路径,需要在Notebook里转存到自己的OBS桶,这一步很容易被忽略。如果是用公共数据集做验证,记得先转存,再指向训练作业。
2.3 训练脚本的“云端适配”:命令行解析、输出目录处理、日志打印规范
训练脚本是整个流程里最需要提前准备的。ModelArts训练作业运行时,会把算法代码从OBS拉取到容器里,然后执行启动命令。所以训练脚本最好是单文件或少量文件的Python工程,而且必须支持命令行参数。我的train.py开头是这样写的:
import argparse import os parser = argparse.ArgumentParser() parser.add_argument('--data_url', type=str, required=True) parser.add_argument('--train_url', type=str, required=True) parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--batch_size', type=int, default=32) args = parser.parse_args() # ModelArts训练环境里,OBS路径需要先通过moxing拷贝到本地 # 再把数据从本地目录读出,可避免每一轮epoch都走网络读数据 import moxing as mox mox.file.copy_parallel(args.data_url, '/home/work/data') train_data_path = '/home/work/data/train/' val_data_path = '/home/work/data/validation/'这里有一个非常关键的细节:ModelArts推荐通过moxing将OBS数据拷贝到容器本地目录,再开始训练。如果不做这一步,而是每次读取图片都直接走OBS的obs://路径,一方面是网络I/O开销大,训练速度会被拖慢很多;另一方面OBS读接口和本地文件系统不完全一致,某些图像库的路径处理会报错。mox.file.copy_parallel会把整个数据目录复制到本地,训练时用本地路径,速度会快一个量级。
训练产物的输出则刚好相反:模型保存时,要先存到容器本地,再拷贝回OBS。因为容器是临时的,任务结束就销毁,不拷贝就什么都没了。我习惯在训练结束后加这一段:
# 保存模型 torch.save(model.state_dict(), 'model.pth') # 把模型拷贝到OBS mox.file.copy('model.pth', os.path.join(args.train_url, 'model.pth'))另外,日志打印也很重要。ModelArts控制台的训练日志是实时滚动的,print()输出会被平台捕获。我喜欢在关键节点打印明确信息,比如“epoch 3/10, loss=0.521, acc=0.847”,这样在训练过程中可以实时观察loss收敛情况,也能在训练失败时快速定位是哪个环节出了问题。如果打印内容太稀疏,比如只打印最终结果,中途卡住或loss发散就很难察觉。
3. 训练作业创建与调参实践:从免费算力到稳定收敛
3.1 创建训练作业时的关键配置项解析
数据准备就绪后,创建训练作业是核心环节。ModelArts创建训练作业时,需要填写一堆配置项:算法来源、训练数据路径、输出路径、训练规格、启动命令等。我第一次创建作业时,面对表单有点懵,整理后其实只有几个关键点:
- 算法来源:可以选择“常用框架”直接写训练代码,也可以选择“自定义镜像”。我建议新手先用常用框架,平台内置了PyTorch、TensorFlow等镜像,省去装环境的过程。
- 数据来源和数据路径:选择“数据存储位置”,指向OBS里的
data目录。这里填的是obs://bucket-name/data,而不是obs://bucket-name,要注意路径指向的层级。 - 训练输出位置:指向
obs://bucket-name/output。训练结束后,模型文件会出现在这个目录。路径不要和代码目录重合,否则会出一些奇怪的权限问题。 - 训练规格:这一步是算力选择,也是成本关键。ModelArts提供多种规格,比如“GPU: 1V100 32GB”“GPU: 1T4 16GB”。我这次验证用的是免费规格,正式训练用的是T4,性价比和显存对ResNet级别模型都是够用的。
启动命令一定要写对,因为你指定的算法来源如果是“常用框架”,平台会根据你选择的框架版本去准备环境,但真正执行的是“启动命令”里给出的内容。我最初犯的错是启动命令只写了python train.py,但脚本需要参数,导致一运行就报缺少--data_url。正确做法是把参数全部放在启动命令里,像这样:
python /home/work/train.py --data_url obs://bucket-name/data --train_url obs://bucket-name/output --epochs 20 --batch_size 64需要注意,代码所在的/home/work目录是平台约定的工作目录,如果你的代码是直接放在code/目录下,启动命令要写python code/train.py,具体取决于你把代码放到了OBS的哪个目录。这里建议创建作业前先跑一遍本地测试,把所有的命令行参数确认好再提交。
3.2 算力规格选择的经验:显存、batch size和训练时长的平衡
算力规格的选择,说白了就是在“花钱”和“等结果”之间做权衡。ModelArts按规格和时长计费,规格越高单价越贵,但训练时间越短。个人实践下来,一个比较务实的做法是:先用小规格(或者免费额度)跑一两个epoch,确认代码和数据没问题,再用目标规格跑完整训练。这样能避免花了大钱跑完整训练,最后发现loss发散,只能推倒重来。
我在训练时遇到过一次比较典型的显存不足。当时选择的规格是16GB显存,batch size设定为128,ResNet50前向传播直接OOM。遇到这种情况,首先把batch size降下来,从128降到64,如果仍然OOM就再降到32。同时,不要忽略混合精度训练,Trainer或PyTorch自带的torch.cuda.amp能减少显存占用,且对精度影响很小。我在实践里开启AMP后,batch size 64跑得相当稳定,训练速度还提升了差不多20%。
还有一个容易被忽略的点是训练时长预估。ModelArts控制台会在训练作业详情页显示当前已运行时长,但这个信息在任务结束后才能看到累计值。如果训练脚本不打印每个epoch耗时,你很难判断还有多久跑完。我的经验是在每个epoch结尾打印耗时和预计剩余时间,哪怕只是一个粗略的估算:
print(f'Epoch {epoch + 1}/{epochs}, loss={running_loss/len(train_loader):.4f}, ' f'acc={correct/total:.4f}, elapsed={elapsed:.1f}s')这样在控制台看到日志后,心里基本有数,也方便决定要不要提前终止训练任务。
3.3 训练日志查看与失败定位:从控制台日志到OBS日志文件
训练作业运行后,控制台会实时显示标准输出。如果代码有异常,错误栈也会出现在这里。一般来说90%的问题都能在控制台日志里定位,比如缺少Python包、路径不存在、显存不足等。但有一个坑是,平台有时会给出很笼统的失败信息,比如“训练作业失败,请查看日志”,这时候需要去OBS里找训练日志文件。
ModelArts会在输出路径的parallel目录下保存日志,路径类似obs://bucket-name/output/log/。我记得有一次脚本里出现了乱码编码问题,控制台日志里只显示了截断的字符,根本看不出原因。后来我把输出路径下的日志文件拖到本地,用编辑器打开,才看到是读取标签文件时的编码格式问题。所以遇到平台日志不够清晰的情况,不要干等着,去OBS目录里翻日志文件,通常能找到更完整的堆栈信息。
调参过程中还有一些值得留意的指标打印。我每次都会打印验证集的accuracy,而不是只看着训练loss。因为loss下降不代表泛化好,可能出现严重的过拟合。如果把训练和验证的指标交替打印,就能直观看到曲线走势。这次我训练了20个epoch,大约到12个epoch时验证集accuracy就不再明显提升,说明模型已经收敛,再往后训练只会增加过拟合风险。这种判断在可视化工具里很容易做,但ModelArts的控制台本身不做曲线展示,要自己从日志里读。
3.4 训练时长和费用控制:免费额度的使用边界与止损技巧
费用控制是我这次特别想分享的一部分。华为云对新用户通常会有一些免费额度,比如免费的训练作业时长和免费的推理实例配额,但注意这些免费额度是有限定规格的,不是所有规格都免费。你选择免费规格时,控制台会显示“免费”标签;如果选了更高规格,会显示按需单价。
对不同需求和预算,我的建议是分场景选择:
- 跑通流程验证代码逻辑:用免费额度里的较小规格,数据量缩小到原来的十分之一,几分钟跑完即可;
- 正式训练中等规模数据:用T4或类似规格,开启AMP,控制batch size在64左右,按时长预估控制成本;
- 需要追求极致训练速度:用更高端的V100/A100规格,但务必确保代码没有问题,不要浪费每一次启动的时长。
我还建议给训练任务设置一个“止损”机制。ModelArts有自动停止的功能,可以在创建训练作业时配置最长运行时间。比如设置最多跑30分钟,如果训练超过时间会自动终止,避免因为写错了epoch数量导致任务跑几小时、费用猛涨。我第一次没设置自动停止,把一个epoch次数写成500的训练作业挂了一晚上,第二天醒来发现还在跑,虽然规格不高,白白烧了不少券。这个教训很深刻。
4. 模型导入与在线服务部署:把模型文件变成可调用的API
4.1 从“训练产物”到“模型版本”:模型导入的两种方式
训练作业跑完,模型权重文件已经在OBS输出目录里了。但此时它还不是一个可以被部署的“模型版本”,需要导入到ModelArts的模型管理里。这一步在控制台的“AI应用”页面里操作,主要有两个入口:一个是“从OBS导入模型”,另一个是“从训练作业导入模型”。
如果是从训练作业导入模型,选择对应的训练作业名称,平台会把输出目录里的模型文件读出来。如果是从OBS导入模型,需要指定一个模型目录,并且这个目录里不光要有权重文件,还要有一个推理配置文件config.json,描述模型输入输出格式和推理依赖。我这次因为没有提前准备config.json,走了不少弯路,后来我专门研究了一下这个文件的格式。
一个标准的推理配置文件大致长这样:
{ "model_algorithm": "image_classification", "metrics": {}, "apis": [ { "protocol": "http", "url": "/", "method": "post", "request": { "Content-type": "application/json", "data": { "type": "object", "properties": { "image_base64": { "type": "string" } } } }, "success_response": { "type": "object", "properties": { "predicted_label": { "type": "string" }, "score": { "type": "number" } } } } ] }光有这个配置文件还不够,你还需要提供一个自定义推理脚本custom_service.py,里面实现模型加载和预测函数。ModelArts把推理服务的骨架都搭好了,你要做的是实现一个_preprocess方法处理输入数据,和一个_postprocess方法转换输出结果。比如输入是JSON里的Base64图片字符串,你在_preprocess里解码成PIL Image,预处理成模型输入的Tensor;推理完成后,在_postprocess里把标签索引映射成可读的名称。
这个过程对没做过模型服务化的人而言可能有点绕。简单来说,config.json是给平台看的“接口描述”,custom_service.py是你自己写的“逻辑实现”,两者配合才能让平台拉起推理服务。
4.2 在线服务配置要点:实例规格、访问控制和API调用
模型导入成功后,可以一键部署为在线服务。部署时你会看到实例规格列表,又牵扯到费用控制。在线服务和训练作业不一样,训练任务跑完自动停止,费用也随之结束;但在线服务是常驻的,只要不删除,就一直计费。个人实践下来的建议是:调试验证时选最小规格的CPU实例,甚至可以用免费额度;确认服务稳定后,如果面向真实应用,再升级成GPU实例,或者直接改用批量推理。
部署完成后,ModelArts会生成一个URL和对应的请求头鉴权信息。调用API时,最常用的方式是用华为云SDK生成认证,然后POST JSON数据到服务地址。我写了一个简单的Python调用示例:
import base64 import requests # 读取图片并转base64 with open('test.jpg', 'rb') as f: img_base64 = base64.b64encode(f.read()).decode('utf-8') # 请求体按config.json里定义的格式 payload = { "image_base64": img_base64 } # 这里简化了鉴权逻辑,实际需要加X-Auth-Token或SDK签名 resp = requests.post( 'https://your-service-url', json=payload, headers={'X-Auth-Token': 'your-token'} ) print(resp.json())实际调用时需要注意两点:一是请求体要和config.json里定义的输入字段一致,二是鉴权签名华为云有专门SDK工具生成,尤其是使用IAM临时凭证时,Token会过期需定期刷新。如果部署后一直返回401或403,先排查鉴权,再排查请求格式。
4.3 服务联调与状态检查:从“部署成功”到“真实可用”的距离
在线服务部署成功后,控制台会显示“运行中”,但这只代表服务进程拉起来了,不代表接口真正可用。我自己就遇到过部署成功但调用报500的情况,原因是custom_service.py里有个导入错误,推理进程启动后初始化失败,但服务的健康检查还没把它判为异常。排查这类问题,最直接的入口是查看服务的日志,ModelArts在线服务的详情页里有日志查看按钮,能看到类似Python异常堆栈的信息。
还有一个常见的坑是模型文件路径问题。当你把模型文件放在某个目录下,比如/home/model/,推理脚本加载模型时用的是相对路径,但服务启动后的工作目录可能不是模型目录。我在脚本里专门加了绝对路径:
import os model_path = os.path.join(os.path.dirname(__file__), 'model.pth') model.load_state_dict(torch.load(model_path, map_location='cpu'))这样能确保无论服务从哪个目录启动,都能正确找到权重文件。调整完推理脚本和配置后,需要重新部署服务才能生效。ModelArts提供了“更新”操作,不用重新导入模型版本,直接更新在线服务配置即可,比重新部署省不少时间。
服务联调时建议准备一张较小的测试图片,比如几十KB的JPG,避免因为图片过大导致请求超时。我最初的测试图是手机拍的大图,调用接口时总要等上几秒,排查了很久才发现是图片Base64后太大,传输和处理都慢了。后来在请求前先压缩图片,整个调用链路的响应时间一下子降下来。
5. 常见问题速查与踩坑实录:故障定位与效率提升技巧
5.1 训练失败类问题:OBS路径错误、依赖缺失、显存不足
训练作业失败是最常见的挫败来源。我整理了自己遇到的几类问题和排查方法,放在这里作为速查表:
| 现象 | 排查方向 | 具体操作 |
|---|---|---|
| 启动命令报ModuleNotFoundError | 镜像里没有代码需要的Python包 | 在代码里加pip install步骤,或改用自定义镜像 |
| 数据读取时报NoSuchKey | OBS路径写错或文件没传上去 | 用obsutills检查目录结构,确认路径层级 |
| 训练中loss为NaN | 学习率过大、数据集有异常值 | 降低学习率,检查标签是否有缺失或越界 |
| 显存溢出OOM | batch size过大、分辨率过高 | 降低batch size,开启混合精度训练,降低图片尺寸 |
| 训练瞬间失败无日志 | 启动命令错误或代码在初始化阶段就异常 | 先在本地用相同参数跑一遍,确认脚本可执行 |
这里面我最想强调的就是“先在本地跑通再上云”。ModelArts的训练容器环境和本地环境有差异,但绝大多数Python逻辑是可以本地先验证的。把启动命令里的参数、数据集路径配好,本地小规模跑一个epoch,确认没有语法错误和读取错误,再提交训练作业。这个习惯能帮你省掉非常多排查时间。
关于依赖缺失,有个小技巧:如果你使用的框架镜像里没有某个包,比如sklearn,可以在训练脚本最前面加一段自动安装逻辑:
import subprocess import sys def install_package(pkg): subprocess.check_call([sys.executable, '-m', 'pip', 'install', pkg, '-q']) install_package('scikit-learn')不过要注意,每次训练作业启动时都会执行这段安装代码,会额外增加启动时间。如果依赖比较多,更推荐使用ModelArts的自定义镜像功能,把依赖提前打进镜像里,这样启动会快很多,但这个操作需要基本的Docker知识,前期可以用自动安装应付。
5.2 部署服务类问题:端口配置、模型加载、并发和时延
部署在线服务后,我遇到过的实际问题主要有几类。一个是端口配置错误。ModelArts在线服务默认监听8080端口,如果你的自定义推理脚本里硬编码了其他端口,服务会启动失败或外部无法访问。建议不要在custom_service.py里手动写app.run(port=xxx),平台的推理框架已经接管了端口设置。
另一个是并发和时延。CPU实例做推理的速度一般比较慢,一张普通图片可能要几百毫秒到一两秒。如果接口被频繁调用,服务会出现排队。ModelArts支持配置多个实例副本,但每个副本都要计费,我现在的经验是先用单实例验证正确性,再评估并发需求。另外,如果对时延敏感,可以考虑模型优化的方向:比如把PyTorch模型转成ONNX或MindSpore格式,或者对输入图片做更积极的前处理压缩。
还有一个隐蔽问题是模型加载方式。如果推理脚本每次请求都重新加载权重文件,服务时延会非常高,因为磁盘I/O是瓶颈。正确的做法是在模型加载阶段,也就是__init__方法里加载一次模型,保存为类成员变量,后续每次请求直接复用。这个优化看起来简单,实际效果是从每次请求几十秒降到几百毫秒。
5.3 效率提升技巧:训练任务版本管理、模型版本复用、使用Notebook快速调试
当训练和部署的目标都跑通后,我开始关注效率问题。第一个心得是给训练作业做版本管理。ModelArts允许你在同一个“训练作业”名称下多次提交任务,每次提交都会生成一个新的版本。这意味着你可以用同一个作业名迭代参数,而不必每次都新建作业。每次修改了代码或数据后,直接“创建新版本”,旧版本记录还在,方便对比不同参数下的训练结果。
第二个心得是用Notebook做快速调试。虽然我前面强调训练作业要写成脚本,但在大规模训练前,先在Notebook里跑一段数据加载代码,检查类别分布、图片尺寸、标签对应关系,可以极大减少低级错误。ModelArts的Notebook和训练作业可以共用同一个OBS桶,调试完的数据路径、代码路径直接复制到训练作业配置里,不会出现路径不一致的问题。
最后想提一个经验是把训练代码和推理代码放在同一个代码仓库里管理。我这次在本地建了个项目目录,里面有train.py、custom_service.py、config.json、requirements.txt,通过obsutil把整个目录上传到OBS。这样训练和部署都指向同一个代码目录,版本一致,不容易出现训练时用一套代码、部署时又用另一套代码的尴尬情况。
6. 实操总结与后续扩展方向
整套流程走完,我最大的感受是ModelArts把“训练”和“部署”之间的距离缩短到了让个人开发者敢去尝试的程度。本地训练一个模型是一回事,但要把它部署成一个公网可调的API,还要处理鉴权、并发、生命周期这些工程问题,门槛并不低。ModelArts把这部分托管之后,个人可以更专注在模型本身,而不是被运维细节困住。
我在实际操作中体会最深的还是在成本控制上。只要记住“用完即停”和“先小后大”这两个原则,ModelArts的花费是完全可以接受的。免费额度拿来验证整个流程,小规格跑真实数据,大规格只用来做最终调优和正式训练。
后续我自己打算在两个方向上继续扩展这个项目。一个是把推理脚本从PyTorch原生模型换成ONNX格式,看看能不能进一步降低部署实例的规格要求,毕竟CPU推理成本和GPU推理成本差距很大;另一个是把ModelArts的在线服务和前端页面结合起来,做一个真正的图片分类小应用,把这次学到的API调用逻辑封装成一个更友好的服务。如果这两个方向有新的进展,我再写一篇实践笔记出来。
最后再分享一个小技巧:如果你准备长期使用ModelArts,建议把常用的数据上传、训练作业创建这些操作写成shell脚本或Python脚本,用华为云的API或SDK来触发,而不是每次都打开控制台点按钮。虽然前期的脚本编写也花了我一些时间,但后续每次迭代的效率和体验完全不一样,尤其是当你需要频繁调整参数重新训练时,脚本化的好处会立刻体现出来。