ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

华为云ModelArts模型训练与部署实战:从ResNet50小样本到边缘低延迟上线

华为云ModelArts模型训练与部署实战:从ResNet50小样本到边缘低延迟上线

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,它内部做了三件事:

  1. Base64解码 → JPEG bytes
  2. OpenCVcv2.imdecode→ BGR numpy array
  3. 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):跑正式训练和部署

关键动作:

  1. 在本地用obsutil命令行工具同步数据:
obsutil cp -r ./data obs://my-bucket/raw/ --update

--update参数只传变更文件,比GUI上传快3倍。

  1. 在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
  1. 用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 Utilization70%-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.2xlargeP4卡对ResNet50推理性价比最高
实例数量2避免单点故障,且负载均衡
最小副本数1保底服务能力
最大副本数5防止突发流量打垮服务
CPU限制4防止预处理抢占GPU资源
内存限制16GiONNX Runtime内存占用约12Gi
超时时间15s容忍预处理耗时
健康检查路径/healthz必须和后处理器一致
环境变量MODEL_PATH=/home/work/model.onnx显式指定路径,避免加载错误

特别注意“环境变量”:ModelArts不会自动把模型文件解压到/home/work/,你必须在部署配置里显式声明MODEL_PATH,否则服务启动失败。

4.5 上线后监控:不止看QPS,要看“业务准确率”

ModelArts的监控面板默认只显示技术指标(QPS、延迟、错误率)。但我们加了一层业务监控:

  1. 在API网关配置里开启“访问日志”,日志投递到LTS(日志服务)
  2. 写个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)
  1. 把defect_count / total_requests作为“业务准确率”,画成折线图

上线第三天,技术指标全绿,但业务准确率从92%掉到87%。查日志发现,产线新换了一批相机,白平衡参数变了,导致输入图像色温偏蓝。我们立刻在预处理器里加了白平衡校正模块,2小时修复。

5. 常见问题与排查技巧实录:那些文档里找不到的“幽灵错误”

5.1 训练阶段高频问题速查表

现象根本原因解决方案经验备注
ImportError: No module named 'modelarts'本地环境没装SDKpip install huaweicloud-sdk-modelarts,且版本必须匹配ModelArts控制台右上角显示的版本号(如2.14.0)我们用2.15.0 SDK,训练任务全失败——版本不匹配是静默错误
OSError: [Errno 12] Cannot allocate memoryLinux内核参数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.csvDataset快照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%,宁可多花点钱,也不能让客户产线停机。这个细节,文档里永远不会写,但它是工业场景的生命线。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进