SageMaker要求容器监听8080端口并提供GET /ping(健康检查,返回200前必须模型加载完毕)和POST /invocations(推理);最常见失败是模型还在后台加载就提前对/ping返回200。
SageMaker 实时端点本质上是围绕一个容器镜像的三次 API 调用,调用本身并不难。决定端点能否进入 InService 状态的,是你提供的镜像能否在足够短的时间内响应两个 HTTP 路由——如果做不到,就要等上十五分钟然后回滚。
SageMaker 不关心镜像内部是什么,只要求容器监听 8080 端口并提供两个路由:
GET /ping — 健康检查。模型加载完毕并可以提供服务后返回 200。这个路由至关重要:SageMaker 在启动容器后会轮询这个接口,只有成功返回才会将端点投入服务。
POST /invocations — 推理。请求体是调用方发送的原始内容(附带其设置的 Content-Type),返回响应体及其内容类型。
最常见的部署失败是这样的:/ping 在大模型还在后台加载时就立即返回 200。SageMaker 标记容器为健康状态、路由一个请求过去、请求失败,你得到一个与模型代码毫无关系的 ModelError。先加载模型,再响应 /ping——用一个在加载结束时设置的布尔标志就能解决,这是全部的修复逻辑。
# A minimal contract, FastAPI
from fastapi import FastAPI, Request, Response
app = FastAPI()
ready = False
@app.on_event("startup")
def load():
global model, ready
model = load_from("/opt/ml/model") # where SageMaker unpacks ModelDataUrl
ready = True
@app.get("/ping")
def ping():
return Response(status_code=200 if ready else 503)
@app.post("/invocations")
async def invocations(request: Request):
payload = await request.json()
return {"prediction": model.predict(payload["inputs"])}
注意 /opt/ml/model。如果在模型上设置了 ModelDataUrl,SageMaker 会在启动容器之前将归档文件下载并解压到该路径。将权重直接 bake 到镜像里,启动快但迭代慢;在运行时自己从 S3 拉取则是最差的选择——因为每次扩容都要重新下载,而且 SageMaker 无法感知这个过程。
模型资源将镜像绑定到一个执行角色,并可选地关联制品和环境变量。
aws sagemaker create-model \
--model-name summariser-v3 \
--execution-role-arn arn:aws:iam::111122223333:role/SageMakerExecutionRole \
--primary-container '{
"Image": "111122223333.dkr.ecr.us-east-1.amazonaws.com/summariser:3.1.0",
"ModelDataUrl": "s3://acme-models/summariser/3.1.0/model.tar.gz",
"Environment": {"MODEL_PRECISION": "bf16", "MAX_BATCH_SIZE": "8"}
}'
使用不可变标签或 digest 来固定镜像。这里的 :latest 意味着六周后的一次扩容事件会悄悄地把不同的模型拉到一半的 fleet 上,而端点的状态中没有任何信息会告诉你这件事。执行角色需要存储库上的 ecr:GetDownloadUrlForLayer 和 ecr:BatchGetImage,以及制品上的 s3:GetObject。
端点配置是实例类型、数量和启动超时时间所在的地方。它一旦创建就是不可变的,所以修改意味着新建一个配置并调用 UpdateEndpoint——而这也正是蓝绿部署得以实现的原因。
aws sagemaker create-endpoint-config \
--endpoint-config-name summariser-v3-ml-g5-xlarge \
--production-variants '[{
"VariantName": "primary",
"ModelName": "summariser-v3",
"InstanceType": "ml.g5.xlarge",
"InitialInstanceCount": 2,
"InitialVariantWeight": 1.0,
"ModelDataDownloadTimeoutInSeconds": 1800,
"ContainerStartupHealthCheckTimeoutInSeconds": 1800
}]'
最后两个字段是任何大型模型都需要刻意设置的。ModelDataDownloadTimeoutInSeconds 限制了拉取和解压 ModelDataUrl 的时间;数 GB 的归档文件可能超过较短的默认值,导致端点失败并抛出关于容器未启动的消息,让你掉进调试容器的坑里。ContainerStartupHealthCheckTimeoutInSeconds 限制了 SageMaker 等待首次成功 /ping 的时间——这是你模型加载时间的预算。在怀疑镜像有问题之前,先把这两个值调大。
InitialVariantWeight 只在存在多个变体时才有用,一个配置上两个加权变体是实现金丝雀部署的方式:将新模型作为第二个变体部署,权重设为 0.05,观察其指标后再转移权重。InitialInstanceCount 为 1 意味着每次部署和每次实例替换都是一次故障,所以对于任何重要的服务来说,2 才是真正的最小值。
aws sagemaker create-endpoint --endpoint-name summariser --endpoint-config-name summariser-v3-ml-g5-xlarge
轮询 describe-endpoint 直到 EndpointStatus 离开 Creating 状态。状态包括:Creating、InService、Updating、RollingBack、Deleting 和 Failed。
如果失败,先读取 describe 响应中的 FailureReason,然后查看 CloudWatch 日志组 /aws/sagemaker/Endpoints/summariser。失败原因通常与健康检查有关;日志流中包含导致问题的异常。
通过创建新的端点配置并调用 update-endpoint 来部署下一个版本。SageMaker 会在销毁旧 fleet 之前启动新 fleet,所以端点名称及其调用方不会改变。
自动缩放需要单独配置——它是针对 SageMakerVariantInvocationsPerInstance 指标的 Application Auto Scaling,不是端点上的一个字段。参见端点自动缩放。
UpdateEndpoint 接受一个可选的 DeploymentConfig,值得设置而不是接受默认值。BlueGreenUpdatePolicy 允许你以金丝雀或线性步骤转移流量,并在步骤之间有一段烘焙时间;AutoRollbackConfiguration 接受一组 CloudWatch 告警,如果在烘焙期间任何一个告警触发,就会中止部署并恢复之前的 fleet。没有这个配置,一个成功加载但回答质量很差的模型对于 SageMaker 来说仍然是一次完全成功的部署——健康检查只知道 /ping 是否返回了 200。用你自己的错误率和延迟指标来设置告警,因为这些是区分"正常运行"和"只是跑着"的唯一信号。
首次部署失败最常见的原因与容器毫无关系。SageMaker 为端点使用维护了自己的每实例类型配额,与 EC2 限制分开,也与同一实例类型上训练和处理作业的配额再次分开。在新账户上,GPU 系列的端点配额经常为零,所以一个完全有效的 CreateEndpoint 会立即失败:
An error occurred (ResourceLimitExceeded) when calling the CreateEndpoint
operation: The account-level service limit 'ml.g5.xlarge for endpoint usage'
is 0 Instances, with current utilization of 0 Instances and a request delta
of 2 Instances. Please use AWS Service Quotas to request an increase for
this quota.
这条消息中有用的部分是引用的配额名称。它是 Service Quotas 中的一个literal条目,每个实例类型一个,人们在提工单时容易搞错后缀:提升 ml.g5.xlarge 的训练作业配额对端点没有作用,两者由不同的团队在不同的时间尺度上审批。从错误信息中读出名称并精确申请。
它是按 Region 的。在 us-east-1 获批的配额提升不适用于 eu-west-1,在多 Region 推广过程中发现这一点可不是什么愉快的下午。
它计算的是实例数,而不是端点数。InitialInstanceCount 为 2 需要至少 2 的配额,而蓝绿更新需要同时容纳两个 fleet 的空间——所以一个恰好达到配额的端点无法在不产生停机的情况下更新。
GPU 配额的提升不是即时生效的。把这个请求当作项目中的前置事项来对待,而不是在上线当天才发现。
默认配额值因账户而异且随时间变化,所以这里故意不引用任何数字。在 Amazon SageMaker 端点和配额参考或 Service Quotas 控制台中检查你账户的当前值,按端点使用情况过滤。
端点不是公共 URL。它是需要 SigV4 签名的 SageMaker Runtime API 调用,这意味着 IAM 控制访问,没有密钥可以泄露。
import boto3, json
rt = boto3.client("sagemaker-runtime", region_name="us-east-1")
response = rt.invoke_endpoint(
EndpointName="summariser",
ContentType="application/json",
Accept="application/json",
Body=json.dumps({"inputs": "Summarise the following ticket: ..."}),
)
print(json.loads(response["Body"].read()))
两个有记载的限制决定了你可以用它做什么。请求体和响应体各自上限为 6,291,456 字节——6 MB。AWS 声明容器必须在 60 秒内响应,模型本身最大处理时间为 60 秒,而且如果你运行在 50 到 60 秒之间,应该将 SDK 的 socket 超时设置为 70。
60 秒这个上限是需要围绕设计的,因为生成模型产生长回答时会超过它。有记载的解决方案是:长时间作业用异步推理,或者通过 InvokeEndpointWithResponseStream 实现流式输出——这样 token 在产生时就能离开容器,而不是最后一次性返回整个响应。
当推理失败时,异常是 HTTP 424 的 ModelError——意味着你的容器返回了 4xx 或 5xx。它携带 OriginalStatusCode、OriginalMessage,以及最有用的 LogStreamArn,直接指向该实例的 CloudWatch 流。在错误处理程序中记录这个 ARN,下一次 ModelError 就可以一键诊断,而不需要去搜索日志。
SageMaker Serverless Inference: Setup and Its Real Limits
Fixing ModelError on a SageMaker Endpoint