ModelError的HTTP状态码是424(Failed Dependency),不是SageMaker的错而是容器返回了4xx/5xx;关键诊断字段是OriginalStatusCode和OriginalMessage,直接指向容器内部错误。
调用 InvokeEndpoint 操作时发生了错误(ModelError):从主节点收到服务器错误(500),消息内容是——通常情况下——一些没什么用的截断内容。这个错误的关键在于:它不是 SageMaker 的错误,而是你的容器的错误,被包装了一层。
InvokeEndpoint API 参考文档用一句话定义了它:"容器中的 Model(由客户拥有)返回了 4xx 或 5xx 错误码。"ModelError 响应本身的 HTTP 状态码是 424 — Failed Dependency,这恰恰是正确的状态码,精确地告诉了你应该往哪里看。SageMaker 成功路由了你的请求,容器接收到它,而容器返回一个错误。
所以那些常规的端点检查都没用。端点是 InService 状态,IAM 权限没问题,实例也健康。出问题的是容器内的代码,以下所有内容都围绕如何找到那段代码自己返回的错误信息。
AWS 文档记载 ModelError 携带三个字段,大多数人从未见过其中两个,因为异常的默认字符串表示不显示它们:
OriginalStatusCode — 你的容器返回的状态码。这是诊断价值最高的字段,因为 400 和 500 的成因完全不同。
OriginalMessage — 你的容器返回的响应体,通常会被截断,有时会包含实际的 Python traceback。
LogStreamArn — 处理请求的实例对应的 CloudWatch 日志流的 ARN。在多实例端点上,这能让你避免读错日志流。
在 boto3 中,它们位于 ClientError 的响应字典里。打印它们而不是异常本身:
from botocore.exceptions import ClientError
try:
resp = rt.invoke_endpoint(
EndpointName="my-endpoint",
ContentType="application/json",
Body=json.dumps(payload),
)
except ClientError as e:
err = e.response["Error"]
if err["Code"] == "ModelError":
print("status :", e.response.get("OriginalStatusCode"))
print("message :", e.response.get("OriginalMessage"))
print("logs :", e.response.get("LogStreamArn"))
raise
在调用点永久记录这三个字段,而不是只在调试时记录。一个被捕获并重新抛出而没有携带这三个字段的 ModelError,是一起你需要复现才能调查的事故。
如果 LogStreamArn 不存在,或者你在查看一小时前的错误,直接去日志组查看。AWS 文档记载端点日志位于 /aws/sagemaker/Endpoints/[EndpointName],日志流名称为 [production-variant-name]/[instance-id]。异步端点会额外添加一个 .../data-log 日志流,推理管道则会为每个容器添加一个日志流。
aws logs tail /aws/sagemaker/Endpoints/my-endpoint \
--since 30m --follow --format short
你的容器写入 stdout 或 stderr 的所有内容都会到这里,这意味着即使 OriginalMessage 截断了,实际的 traceback 也会在这里。除了明显的异常之外,有两个需要关注的东西:
失败之前的那个请求。大多数 serving 容器会记录每次调用,所以 traceback 之前最后一条成功的日志行会告诉你出问题的输入长什么样。
worker 是否重启过。一个在请求之间崩溃并重启的容器会产生间歇性的 ModelError 响应,看起来像跟负载有关但实际上不是。同时间段交叉检查 MemoryUtilization — OOM kill 是常见原因,而且它并不总是留下 Python traceback。
首先按 OriginalStatusCode 分支。这能把搜索空间减半。
容器返回 4xx 意味着请求有问题。几乎都是容器的输入函数无法解析的 payload。容易让人踩坑的典型模式是你发送的 ContentType 和容器构建时预期接受的不匹配:一个期望 text/csv 的容器收到 application/json 会拒绝一个完全有效的 payload。另一个常见的 4xx 是 schema 不匹配 — 内容类型对了但键名不对,或者键名对了但嵌套形状是容器反序列化器处理不了的。用容器自身文档中的示例 payload 调用来复现;如果那样能用而你的不行,差异就是你的 bug。
容器返回 5xx 意味着代码挂了。常见原因按大致频率排序:推理函数中未捕获的异常,最常见的是第一个真实输入的 shape 或 dtype 不匹配;OOM kill,payload 比测试过的任何情况都大;容器预期在模型构件中找到但找不到的文件 — 如果加载是惰性的,它会在第一次请求时而不是加载时失败;以及一个在请求时才导入但未安装的依赖。所有四种都能在日志流中看到 traceback。
有一种 5xx 原因不是你的代码 bug:请求超过了容器的响应窗口。AWS 文档记载模型容器必须在 60 秒内响应。可靠地需要更长时间的工作应该放到异步推理上,那里的超时是按请求参数而不是固定上限。如果同样的 payload 小的时候成功、大的时候失败,在怀疑模型之前先怀疑这个。
ModelNotReadyException(HTTP 429)。根本不是你容器中的错误。AWS 文档记载它的含义是:无服务器变体的资源仍在配置中,或者多模型端点仍在下载或加载目标模型。文档记载的响应是等待并重试。不要把它纳入你的限流逻辑。
ValidationError(HTTP 400)。SageMaker 在请求到达你的容器之前就拒绝了它 — body 超过 6,291,456 字节限制、不存在的端点名称、端点不在服务状态。你的容器日志中不会有任何相关内容,因为你的容器根本没见到这个请求。
ServiceUnavailable(HTTP 503)和 InternalFailure(HTTP 500)。这些是 SageMaker 的,不是你的。用退避重试。如果它们持续出现而不是间歇性出现,那是支持工单,不是代码改动。
让以上所有内容快速定位的纪律是按错误码分支,而不是按消息文本分支。ModelError 去你的容器日志,ModelNotReadyException 去一个短的有界重试,ValidationError 作为 400 返回给调用方,5xx 家族去退避重试。在客户端写一次,这能把三小时的调查变成一行日志,准确指出该看哪里。