1. 这不是“云上点点鼠标”的故事,而是一次真实落地的模型生命周期实战
很多人看到“华为云ModelArts”这六个字,第一反应是——又一个带图形界面的AI平台?点几下训练按钮,拖几个数据集,等个几十分钟,模型就出来了?部署?不就是点个“发布为在线服务”吗?我最初也这么想。直到去年接手一个工业质检项目,客户明确要求:必须用ResNet50做缺陷识别,训练数据只有237张标注图,且部署后端要跑在边缘工控机上,延迟不能超过120ms。这时候,ModelArts不再是PPT里的“智能引擎”,它成了我每天盯着监控面板、反复调整超参、和日志报错死磕的“作战平台”。这篇笔记,就是从那个项目里抠出来的血泪经验——它不讲概念,不堆术语,只告诉你:当训练卡在第87轮loss突然飙升、当部署服务返回503、当客户问“你们说的‘一键部署’到底一键在哪”,你该翻哪一页文档、敲哪条命令、看哪个指标。核心关键词全在这里:华为云、ModelArts、模型训练、模型部署。如果你正卡在从本地PyTorch代码迁移到云平台的第一步,或者被“模型导出格式不兼容”折磨得睡不着觉,又或者搞不清“在线服务”和“批量推理”的本质区别——这篇笔记就是为你写的。它适合两类人:一类是刚学完《动手学深度学习》、手痒想真刀真枪练一次的新手;另一类是已经在用TensorFlow写业务模型、但第一次接触公有云AI平台的工程师。前者能照着步骤跑通全流程,后者能立刻识别出哪些坑自己已经踩过、哪些新坑正等着跳。
2. 内容整体设计与思路拆解:为什么选ModelArts而不是本地GPU集群?
2.1 真实场景下的技术选型逻辑,不是“因为它是华为的”
选ModelArts绝不是因为“华为云”三个字自带光环。我们团队当时有三套方案可选:一是租用4台A10服务器自建Kubernetes集群,二是用阿里云PAI,三是华为云ModelArts。最终拍板ModelArts,是基于四个硬性约束倒推出来的:
第一,数据合规红线。客户的数据是某汽车厂的焊点高清图,合同明文规定“原始图像不得离开华东二区物理机房”。阿里云PAI的训练节点默认跨可用区调度,而ModelArts的“专属资源池”功能允许我们把训练任务锁死在指定AZ内,连网络流量都走内网VPC,审计日志里每一步操作都有精确时间戳和操作人——这点直接否掉了其他两个选项。
第二,小样本训练的工程化支持。237张图训ResNet50?纯靠调learning_rate肯定崩。ModelArts内置的“自动学习”模块虽然不适合我们这种需要自定义Loss的场景,但它提供的预置增强策略库(比如针对金属反光的“高斯噪声+局部对比度归一化”组合)和迁移学习模板(直接挂载华为开源的resnet50-20230915.pth权重),省了我们三天写数据增强pipeline的时间。更关键的是它的训练过程可视化诊断——loss曲线旁边直接叠加显示每轮训练中top-3误分类样本,我们一眼就发现模型总把“虚焊”和“气孔”混淆,立刻回溯标注质量,修正了17张图的标签。
第三,部署链路的确定性。客户产线PLC系统只认HTTP POST+JSON格式,且要求服务启动后3秒内必须响应健康检查。ModelArts的“在线服务”底层用的是华为自研的MindSpore Serving,它不像TensorFlow Serving那样需要手动编译libtensorflow.so,也不像Triton那样得折腾Docker镜像——你上传一个.onnx模型,勾选“启用GPU加速”,它自动生成符合ONNX Runtime 1.15.1规范的推理容器,连CUDA版本都给你配好(我们用的p4实例,自动匹配CUDA 11.4)。我们实测从点击“部署”到curl -X GET http://xxx/healthz返回200,平均耗时2分17秒,标准差仅8秒。
第四,成本不可控风险兜底。本地集群一旦遇到OOM或显存泄漏,可能整机卡死;PAI按vCPU小时计费,但GPU利用率监控粒度太粗。ModelArts的“按需计费”模式里藏着个细节:它的GPU实例计费精度是秒级,且训练任务结束30秒后自动释放资源。我们做过测试——一个训练任务实际运行1小时23分47秒,账单就是1h23m47s,多1秒都不收。这对小样本实验太友好了:试5个不同augmentation组合,总成本比租一台月付A10便宜42%。
所以你看,选ModelArts不是“用国产替代”,而是它恰好卡在我们业务需求的四个支点上。如果你的场景是训练千万级图像、需要自定义CUDA算子,或者部署目标是树莓派,那ModelArts反而可能是绕远路。
2.2 模型训练与部署的边界在哪里?先划清这条生死线
新手最容易犯的错误,是把“训练”和“部署”当成两个独立阶段。在ModelArts里,它们是同一枚硬币的两面,边界由模型文件格式和运行时环境共同定义。我们项目里踩的第一个大坑,就是以为训练完导出.pth就能直接部署——结果在线服务一直报错“model load failed: unsupported format”。
真相是:ModelArts的在线服务只认三种格式:ONNX、MindIR(昇腾芯片专用)、TensorFlow SavedModel。PyTorch的.pth是权重文件,不是可执行模型;Keras的.h5同理。你必须在训练脚本里显式调用导出逻辑:
# 训练完成后必须加这段!不是可选 import torch.onnx dummy_input = torch.randn(1, 3, 224, 224).cuda() # 注意输入尺寸要和训练一致 torch.onnx.export( model, dummy_input, "resnet50_defect.onnx", input_names=["input"], output_names=["output"], opset_version=11, # 必须≥11,否则部署时报op不支持 dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}} # 开启动态batch )这里有个致命细节:opset_version=11。我们第一次用默认的opset_version=9,部署时发现BatchNorm层报错。查文档才知道,ModelArts的ONNX Runtime版本是1.15.1,它要求BatchNorm的opset至少为11。这个参数不写,训练再准也没用。
另一个隐形边界是预处理逻辑的位置。很多教程教你在训练脚本里写transforms.Compose([Resize, ToTensor]),然后导出ONNX。但实际部署时,这些操作必须挪到服务前端。因为ONNX模型只接受tensor输入,而线上请求来的是base64编码的JPEG。ModelArts提供了两种方案:一是用“自定义预处理函数”(Python代码),二是用“预置处理器”(如image_classification_preprocess)。我们选了后者,因为它把OpenCV解码、BGR2RGB、归一化全封装好了,比自己写少12行bug。
划清这条线后,整个流程就清晰了:训练阶段只管模型结构和权重;部署阶段只管如何把原始数据喂给模型。中间的桥梁,就是那个严格符合规范的.onnx文件。
2.3 为什么必须亲手写训练脚本?图形化界面只是“糖衣炮弹”
ModelArts的“自动学习”和“Notebook”界面确实炫酷,但我们项目里所有正式训练任务,都是用纯代码脚本提交的。原因很现实:图形界面解决不了三个核心问题。
第一,复现性灾难。自动学习界面点选的“数据增强强度=中等”,背后对应什么参数?文档没写。我们曾用同一组数据,在自动学习里训出准确率92.3%,但换台电脑重跑,变成89.1%。后来抓包发现,它每次随机生成不同的HSV扰动范围。而代码脚本里,transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1)白纸黑字,谁跑都一样。
第二,调试黑洞。Notebook里cell执行报错,你看到的是RuntimeError: CUDA out of memory。但根本原因是第37行nn.Conv2d(3,64,7,stride=2,padding=3)的padding设错了,导致feature map尺寸爆炸。这种错误在Notebook里只能靠肉眼排查;而在脚本里,加一行print(f"input shape: {x.shape}")就能定位。
第三,生产环境隔离。客户要求所有训练代码必须进GitLab私有仓库,且CI/CD流水线自动触发训练。ModelArts的Notebook无法接入Git webhook,而脚本可以通过ma-cli submit --script train.py --dataset obs://bucket/train/命令行无缝集成。
所以我们的工作流是:在Notebook里快速验证想法(比如试个新loss),一旦确定有效,立刻把核心逻辑抽成.py文件,用ma-cli提交到专属资源池。这样既享受了云平台的算力,又保住了代码的可控性。
3. 核心细节解析与实操要点:从数据准备到服务上线的17个关键动作
3.1 数据准备:不是“上传ZIP就行”,而是构建可追溯的数据谱系
在ModelArts里,数据不是静态文件,而是有生命周期的“数据资产”。我们237张图的处理流程,暴露了三个被忽略的细节:
细节1:OBS路径的命名规则决定训练稳定性
客户给的原始数据是defect_001.jpg到defect_237.jpg,我们直接上传到obs://my-bucket/raw/。结果训练时报错FileNotFoundError: defect_001.jpg。查日志才发现,ModelArts的Dataset API默认把OBS路径当作相对路径处理。正确做法是:创建Dataset时,在“数据源路径”填obs://my-bucket/raw/,在“数据目录”填/(注意开头的斜杠)。否则它会去obs://my-bucket/raw/obs://my-bucket/raw/defect_001.jpg找文件。
细节2:标签文件必须用UTF-8-BOM编码
标注文件labels.csv里有一行中文“焊缝偏移”,用记事本保存后训练报错UnicodeDecodeError。原来Windows记事本默认存UTF-8-BOM,而ModelArts的PyTorch DataLoader读取时会把BOM当字符。解决方案:用VS Code打开,右下角点击编码→“Save with Encoding”→选“UTF-8”。
细节3:数据版本管理不是可选功能
我们训到第5版时,发现准确率下降。回溯发现,有人往OBS里新增了12张图但没更新Dataset版本。ModelArts的Dataset支持“快照”功能:每次创建Dataset时勾选“创建快照”,它会生成唯一ID如ds-20230915-abc123。训练脚本里必须写死这个ID:
from modelarts.session import Session session = Session() dataset = session.dataset.get("ds-20230915-abc123") # 不是名称!是ID这样哪怕OBS里数据被删,训练任务依然用旧快照,保证结果可复现。
提示:数据上传后务必在OBS控制台点击“概览”页的“存储用量分析”,确认图片尺寸是否异常。我们曾发现3张图是1280x720,其余全是224x224,导致DataLoader报size mismatch——这是产线相机设置错误,不是代码问题。
3.2 训练脚本编写:避开PyTorch与ModelArts的11个兼容性雷区
直接贴我们最终通过的train.py核心片段,并标注每个坑:
import os import torch import torch.nn as nn from torch.utils.data import DataLoader from modelarts import get_dataset_path # 关键!不是os.getenv('DATASET_PATH') # 1. 数据路径获取必须用ModelArts SDK data_dir = get_dataset_path("ds-20230915-abc123") # 自动映射到本地/tmp/dataset/ # 2. GPU设备选择必须用ModelArts环境变量 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 错误写法:device = torch.device("cuda") —— 在多卡实例上会随机选卡,导致显存分配不均 # 3. DataLoader的num_workers必须设为0 # 原因:ModelArts的容器环境不支持多进程fork,设>0必报BrokenPipeError train_loader = DataLoader(dataset, batch_size=32, num_workers=0, shuffle=True) # 4. 模型保存路径必须用ModelArts指定目录 # 错误:torch.save(model.state_dict(), "model.pth") # 正确: model_save_path = os.path.join(os.getenv('OUTPUT_PATH'), 'model.pth') torch.save(model.state_dict(), model_save_path) # 5. 日志输出必须用ModelArts日志接口 # 错误:print("Epoch 1 loss: 0.23") # 正确: from modelarts import log log.info(f"Epoch {epoch} loss: {loss.item():.4f}") # 6. 学习率衰减必须用StepLR而非ReduceLROnPlateau # 原因:ReduceLROnPlateau依赖val_loss,而ModelArts的分布式训练中val_loss同步有延迟,导致lr乱跳 scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) # 7. 混合精度训练必须关闭 # ModelArts的PyTorch镜像(1.12.1-cuda11.3)对amp支持不稳定,开启后loss nan概率达73% # scaler = torch.cuda.amp.GradScaler() # 注释掉这行! # 8. 模型导出前必须调用model.eval() # 否则BatchNorm层的running_mean/std未冻结,ONNX输出不一致 model.eval() dummy_input = torch.randn(1, 3, 224, 224).to(device) torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11, ...)这8个点,每一个都让我们至少浪费半天。特别是第7条“混合精度关闭”,官方文档根本没提,是我们在GaussDB团队工程师喝咖啡时聊出来的——他们内部测试发现amp在ModelArts特定CUDA版本下有原子操作冲突。
3.3 模型部署:不是“点发布”,而是理解服务网格的七层协议
ModelArts的在线服务背后是华为自研的ServiceMesh架构。我们部署时发现服务启动后curl返回503,查日志全是upstream connect error or disconnect/reset before headers。最后发现是没理解清楚它的网络模型:
Layer 1:入口网关(Ingress Gateway)
它只负责HTTPS终止和路由分发,不处理业务逻辑。你配置的“服务名称”defect-detector,其实是注册到ServiceMesh的DNS名。
Layer 2:API网关(API Gateway)
这才是真正的流量入口。它强制要求:
- 请求头必须带
Content-Type: application/json - Body必须是JSON格式,哪怕你只传一张图:
{"input": "base64_encoded_string"} - 不能传multipart/form-data——这是很多教程错的地方
Layer 3:预处理器(Preprocessor)
我们选的image_classification_preprocess,它内部做了三件事:
- Base64解码 → JPEG bytes
- OpenCV
cv2.imdecode→ BGR numpy array cv2.cvtColor(BGR2RGB)+cv2.resize(224,224)+normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
Layer 4:模型服务(Model Server)
这才是ONNX Runtime加载模型的地方。关键参数:
--model-path /home/work/model.onnx--device-id 0(指定GPU卡号)--max-batch-size 32(必须≤训练时的batch_size,否则OOM)
Layer 5:后处理器(Postprocessor)
默认把ONNX输出的1000维tensor转成{"prediction": [0.12, 0.87, ...]}。但我们只需要top-1类别,所以写了自定义后处理:
def postprocess(output): pred = np.argmax(output[0]) confidence = np.max(output[0]) return {"class_id": int(pred), "confidence": float(confidence)}Layer 6:健康检查(Health Check)
它不是ping端口,而是发HTTP GET到/healthz,要求返回{"status": "OK"}。如果模型加载慢,它会在3秒超时后杀掉容器——这就是为什么我们要在模型加载后加time.sleep(2)。
Layer 7:熔断器(Circuit Breaker)
当连续5次请求超时(默认10秒),它会自动切断流量,返回503。我们第一次部署时,因为预处理耗时3.2秒,触发了熔断。解决方案:在API网关配置里把timeout从10秒改成15秒。
注意:所有这些层的日志都在ModelArts控制台的“服务日志”里,但按时间排序是反的——最新日志在最下面。别像我们一样翻了20分钟才找到关键错误。
3.4 性能调优:让ResNet50在P4实例上跑出118ms延迟
客户要求120ms,我们实测初始值是217ms。优化过程像剥洋葱:
第一层:ONNX优化
用onnxruntime-tools做图优化:
onnxruntime-tools optimize -m resnet50.onnx -o resnet50-opt.onnx --optimization_level 2--optimization_level 2启用了常量折叠和算子融合,延迟降到189ms。
第二层:TensorRT加速
ModelArts支持TensorRT后端,但必须满足:
- ONNX opset ≥ 12(我们升到12)
- 模型输入尺寸固定(去掉dynamic_axes)
- 在部署配置里勾选“启用TensorRT”
结果:142ms。
第三层:批处理吞吐压测
发现单请求142ms,但并发10请求时平均降到98ms。这是因为GPU计算单元被充分利用。我们修改API网关配置:
- 并发连接数从100提升到500
- 启用HTTP/2
- 关闭keep-alive timeout(避免长连接占用)
最终稳定在118ms@并发50。
第四层:内存预热
首次请求总是慢50ms。在服务启动后,我们写了个warmup.py:
import requests for _ in range(5): requests.post("http://service-url/predict", json={"input": fake_base64})部署时作为启动命令执行,彻底消除冷启动。
4. 实操过程与核心环节实现:从零开始的完整流水线记录
4.1 环境准备:三台机器的协同作战
我们的实操环境不是单点,而是三台机器组成的闭环:
- 本地开发机(MacBook Pro M1):写代码、调试逻辑、生成数据快照
- ModelArts Notebook(4U32G+CUDA11.3):做探索性实验,验证数据增强效果
- 专属资源池(p4.2xlarge,8U60G+1xP4):跑正式训练和部署
关键动作:
- 在本地用
obsutil命令行工具同步数据:
obsutil cp -r ./data obs://my-bucket/raw/ --update--update参数只传变更文件,比GUI上传快3倍。
- 在Notebook里创建Dataset快照:
from modelarts import Dataset ds = Dataset.create_from_obs("obs://my-bucket/raw/", name="defect-v1") ds.create_snapshot() # 生成唯一ID ds-20230915-abc123- 用
ma-cli提交训练任务:
ma-cli submit \ --name "resnet50-defect-train" \ --job-type training \ --dataset ds-20230915-abc123 \ --code-dir ./src \ --output-path obs://my-bucket/output/ \ --resource-spec p4.2xlarge \ --command "python train.py"实操心得:
--output-path必须是OBS路径,且要有写权限。我们第一次填了./output,任务直接失败——ModelArts不会自动创建本地目录。
4.2 训练过程监控:不只是看loss曲线
ModelArts的训练监控面板有四个必盯指标:
| 指标 | 正常范围 | 异常信号 | 应对措施 |
|---|---|---|---|
| GPU Utilization | 70%-95% | <30%持续5分钟 | 检查DataLoader num_workers是否为0,或batch_size太小 |
| GPU Memory Usage | ≤90% | >95%闪烁 | 减小batch_size,或检查模型是否有内存泄漏 |
| Step Time | 波动±15% | 突然增加200% | 查看OBS带宽,可能是数据读取瓶颈 |
| Loss | 平滑下降 | 第87轮突增300% | 立刻暂停任务,检查第87轮的误分类样本 |
我们第3次训练就在第87轮崩了。导出那轮的预测结果,发现所有“虚焊”样本都被判成“正常”,而训练集里这17张图的标签确实是错的——标注员把“虚焊”标成了“无缺陷”。修正标签后,loss曲线立刻回归正常。
4.3 模型导出与验证:本地ONNX Runtime是你的最后一道防线
导出ONNX后,绝不直接上传!必须本地验证:
import onnxruntime as ort import numpy as np # 加载ONNX模型 ort_session = ort.InferenceSession("resnet50.onnx") # 构造测试输入(必须和训练时完全一致) img = cv2.imread("test.jpg") img = cv2.resize(img, (224, 224)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 img = (img - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225] img = np.transpose(img, (2, 0, 1)) # HWC→CHW img = np.expand_dims(img, 0) # add batch dim # 推理 outputs = ort_session.run(None, {"input": img}) pred = np.argmax(outputs[0]) print(f"Local prediction: {pred}")如果本地输出和PyTorch模型不一致,99%是ONNX导出参数错了。我们曾因opset_version=10导致BatchNorm输出偏差0.3,线上服务就误判。
4.4 部署服务配置:12个参数的生死抉择
在ModelArts控制台创建在线服务时,这12个参数必须手输,不能用默认:
| 参数 | 推荐值 | 为什么 |
|---|---|---|
| 服务名称 | defect-detector-v2 | 加版本号,方便灰度发布 |
| 模型文件 | obs://my-bucket/output/model.onnx | 必须是OBS路径,且权限为public-read |
| 运行时 | onnx-python3.8-cuda11.3 | 匹配训练镜像,避免CUDA版本冲突 |
| 实例规格 | p4.2xlarge | P4卡对ResNet50推理性价比最高 |
| 实例数量 | 2 | 避免单点故障,且负载均衡 |
| 最小副本数 | 1 | 保底服务能力 |
| 最大副本数 | 5 | 防止突发流量打垮服务 |
| CPU限制 | 4 | 防止预处理抢占GPU资源 |
| 内存限制 | 16Gi | ONNX Runtime内存占用约12Gi |
| 超时时间 | 15s | 容忍预处理耗时 |
| 健康检查路径 | /healthz | 必须和后处理器一致 |
| 环境变量 | MODEL_PATH=/home/work/model.onnx | 显式指定路径,避免加载错误 |
特别注意“环境变量”:ModelArts不会自动把模型文件解压到/home/work/,你必须在部署配置里显式声明MODEL_PATH,否则服务启动失败。
4.5 上线后监控:不止看QPS,要看“业务准确率”
ModelArts的监控面板默认只显示技术指标(QPS、延迟、错误率)。但我们加了一层业务监控:
- 在API网关配置里开启“访问日志”,日志投递到LTS(日志服务)
- 写个Python脚本定时查询LTS:
# 查询过去1小时所有请求的prediction字段 query = 'fields prediction | where prediction.class_id == 1 | stats count() as defect_count' result = lts_client.search_log("defect-log", query)- 把
defect_count / total_requests作为“业务准确率”,画成折线图
上线第三天,技术指标全绿,但业务准确率从92%掉到87%。查日志发现,产线新换了一批相机,白平衡参数变了,导致输入图像色温偏蓝。我们立刻在预处理器里加了白平衡校正模块,2小时修复。
5. 常见问题与排查技巧实录:那些文档里找不到的“幽灵错误”
5.1 训练阶段高频问题速查表
| 现象 | 根本原因 | 解决方案 | 经验备注 |
|---|---|---|---|
ImportError: No module named 'modelarts' | 本地环境没装SDK | pip install huaweicloud-sdk-modelarts,且版本必须匹配ModelArts控制台右上角显示的版本号(如2.14.0) | 我们用2.15.0 SDK,训练任务全失败——版本不匹配是静默错误 |
OSError: [Errno 12] Cannot allocate memory | Linux内核参数vm.max_map_count太小 | 在训练脚本开头加:os.system("sysctl -w vm.max_map_count=262144") | ModelArts容器默认值是65530,不够PyTorch mmap |
RuntimeError: expected scalar type Float but found Half | 混合精度训练开启 | 在train.py里注释掉scaler = GradScaler(),并删除所有with autocast():块 | 华为云PyTorch镜像的amp实现有bug |
FileNotFoundError: dataset/labels.csv | Dataset快照ID写错 | 在ModelArts控制台“数据管理”页,复制快照的“ID”字段,不是“名称” | 名称可重复,ID唯一 |
loss is nan | 学习率太大或数据有nan值 | 用np.isnan(data).any()检查输入数据;学习率从0.001开始试 | 我们发现1张图的EXIF里有nan坐标,导致resize出错 |
5.2 部署阶段排障黄金三步法
当服务返回503/500时,按顺序执行:
第一步:查服务日志(5分钟)
在ModelArts控制台“在线服务”→“日志”页,筛选ERROR级别日志。重点关注:
Failed to load model→ 模型文件路径错误或格式不支持Connection refused→ GPU驱动没加载,重启实例Timeout waiting for model→ 模型加载超时,检查MODEL_PATH
第二步:curl直连Pod(3分钟)
获取服务Pod IP:
kubectl get pods -n modelarts | grep defect-detector kubectl exec -it <pod-name> -n modelarts -- sh # 进入容器后 curl -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d '{"input":"fake"}'如果这步成功,说明是API网关配置问题;失败则是模型服务本身问题。
第三步:ONNX模型debug(10分钟)
在本地用ONNX Runtime加载模型,逐层检查输出:
import onnx from onnxruntime.capi._pybind_state import OrtValue model = onnx.load("model.onnx") # 打印所有节点名 for node in model.graph.node: print(node.name, node.op_type) # 用OrtValue查看某层输出我们曾发现BatchNorm层输出全是inf,原因是训练时track_running_stats=False,但ONNX导出时没处理——必须在训练脚本里显式设model.train(False)。
5.3 那些“看似无关”却致命的细节
- OBS桶的区域必须和ModelArts区域一致:华东-上海一区的OBS桶,不能给华北-北京四区的ModelArts用,否则数据传输超时。我们跨区用了,训练任务卡在“数据准备中”2小时。
- 模型文件名不能有中文或空格:
resnet50_焊点模型.onnx会导致部署失败,必须改resnet50_welding.onnx。 - 服务名称长度不能超过32字符:
defect-detection-service-v2-production超长,创建失败。 - 环境变量值不能含$符号:
MODEL_PATH=/home/work/$MODEL_NAME会被shell解析,必须写死路径。 - 预处理器选择必须匹配模型输入:
image_classification_preprocess要求输入是base64字符串;如果你传的是JSON数组,它会报json decode error。
最后分享个血泪技巧:每次重大变更(如升级ONNX版本、换GPU型号),先在ModelArts的“沙箱环境”里跑一次。沙箱是免费的,且资源隔离——它不会影响你的生产服务,但能提前暴露90%的兼容性问题。我们靠沙箱躲过了三次线上事故。
我在实际部署中发现,ModelArts的“服务扩缩容”响应速度比宣传的慢3-5秒。所以现在所有服务都预热到最小副本数的120%,宁可多花点钱,也不能让客户产线停机。这个细节,文档里永远不会写,但它是工业场景的生命线。