Skip to content

Serverless 推理:AWS Lambda 部署与冷启动攻坚

本页速览 低频、突发、按调用计费的推理场景用 Serverless 最划算。本文实战 AWS Lambda(或云函数)部署推理服务,重点解决冷启动,并对比常驻服务的成本模型。

Serverless 推理:AWS Lambda 部署与冷启动攻坚

一句话定义:Serverless 推理是把模型跑在"按调用计费、自动扩缩到零"的无服务器平台(如 AWS Lambda)上——没有常驻实例,来了请求才拉起,跑完就释放

为什么值得动手:如果你的推理流量是"低频、突发、实验性"的,常驻 GPU 实例的钱就是白烧的——一个 A10 实例月租几千元,每天却只有几十次调用,成本利用率不到 1%。Serverless 把这些场景的成本降到"一次调用几分钱",还能自动扛住突发。本文用 AWS Lambda 容器镜像部署一个图像分类服务,重点攻克冷启动,最后给出与常驻服务的成本分界点。

一、Serverless 推理适用场景

先给结论,四类场景适合:

  1. 低频业务:每天几百~几千次调用,没有稳定流量曲线;
  2. 突发流量:秒杀、活动、批量任务峰谷差 100 倍以上;
  3. 实验性模型:验证期模型,随时废弃,不值得长期挂实例;
  4. 事件驱动:模型由消息/对象存储事件触发(图片上传即打标)。

不适用:稳定高 QPS 的核心链路(Lambda 并发上限 + 单实例吞吐低于常驻 GPU,QPS 一高反而贵且慢)、需要 GPU 的模型(Lambda 无 GPU,见第五节)、毫秒级超低延迟(冷启动 + 网络跳数多)。

场景判断框架与部署架构模式里"事件驱动/无服务器模式"对应。

二、架构与打包:Lambda 容器镜像

text
API Gateway ──▶ AWS Lambda(容器镜像)──▶ 模型推理 ──▶ 返回结果

Lambda 支持自定义容器镜像(镜像 ≤ 10GB,模型和依赖全打进),比 ZIP 包(250MB 上限)适合带权重的模型。Dockerfile:

dockerfile
# 必须用 AWS 提供的 Lambda 基础镜像
FROM public.ecr.aws/lambda/python:3.11
WORKDIR /var/task

COPY requirements.txt .
# 注意:模型权重也在镜像里(模型不大时最省事)
COPY models/resnet18.pth models/
COPY app/ app/

# 自定义运行时入口指向 handler
CMD ["app.handler.lambda_handler"]
python
# app/handler.py
import io
import torch
from PIL import Image
import torchvision.transforms as T
from torchvision.models import resnet18

# 全局单例:冷启动时加载一次,同一实例内的热请求复用
_model = None

def _get_model():
    global _model
    if _model is None:
        model = resnet18(weights=None)
        model.load_state_dict(torch.load("/var/task/models/resnet18.pth", map_location="cpu"))
        model.eval()
        _model = model
    return _model

def lambda_handler(event, context):
    import base64
    body = event.get("body", "")
    img_bytes = base64.b64decode(body)

    transform = T.Compose([T.Resize(256), T.CenterCrop(224), T.ToTensor(),
                           T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
    tensor = transform(Image.open(io.BytesIO(img_bytes)).convert("RGB")).unsqueeze(0)
    with torch.inference_mode():
        logits = _get_model()(tensor)
    top5 = torch.topk(torch.softmax(logits, dim=1)[0], k=5)
    return {"statusCode": 200,
            "body": [{"score": float(s), "index": int(i)} for s, i in zip(top5.values, top5.indices)]}

FastAPI 服务对照:同样的"模块级加载 + 单例模型"原则,但 Lambda 的并发模型是"实例数由平台扩缩",每个实例内只有一个并发请求在跑(默认),所以模块级加载的收益在实例复用期间——冷启动只付一次加载成本,随后几十秒内的请求都走热路径。

三、冷启动:头号敌人

冷启动时间 = 容器拉起 + 运行时初始化 + 模型加载。参考数字(ResNet18 CPU):

阶段耗时
容器/运行时拉起(平台侧)300ms~2s
Python + PyTorch 导入2~5s
模型权重加载0.5~2s
冷启动合计约 3~8s(热请求 < 100ms)

四个手段,按成本从低到高:

  1. 预热(concurrency warm-up):定时器(如每 2 分钟一个 EventBridge 规则)打一个探活请求,让实例保持热状态——零成本,但没流量时实例也会被回收,只治标;
  2. Provisioned Concurrency(预留并发):提前 N 个实例就绪,冷启动从路径里消除——按预留量计费,把省下的钱又花回去一部分,适合"必须稳"的核心场景;
  3. 减少冷启动本身:用 Amazon Linux 2 类镜像、避免冷门依赖、模型从 S3 惰性加载(不随镜像、用 --mount-type 挂载)或换用更快运行时(如把 CPU 模型换成 ONNX Runtime、用 Rust/Go 写 handler);
  4. 函数超时与内存调大:Lambda 超时上限 900s(15 分钟),默认 3s 肯定不够模型加载;内存调到 1024MB 以上也提高 CPU 配额(内存与 CPU 成正比)。

冷启动雪崩

突发流量瞬间拉起几十个实例,每个实例都要跑一次 3~8 秒的冷启动——第一个请求的 P99 可能 8 秒+。缓解:预留并发只保最低水位、网关层对冷启动请求做降级提示、业务侧用异步(先返回 task_id 再回调结果)。别指望"压测时冷启动会自己消失"。

事件触发:把"请求-响应"改成"事件-完成"

低频推理的另一个常见形态是事件驱动——对象存储上传、消息队列到件即触发推理,不必等同步返回。Lambda 的 S3/事件触发天然支持:

python
# 图片上传 S3 即触发打标,结果写回另一个桶或数据库
import boto3
from PIL import Image
import io

s3 = boto3.client("s3")

def lambda_handler(event, context):
    for record in event.get("Records", []):
        bucket = record["s3"]["bucket"]["name"]
        key = record["s3"]["object"]["key"]
        obj = s3.get_object(Bucket=bucket, Key=key)
        predictions = predict(obj["Body"].read())   # 复用第三节的 predict
        s3.put_object(Bucket=bucket, Key=key.replace("uploads/", "tags/"),
                      Body=json.dumps(predictions))
    return {"statusCode": 200}

事件驱动的好处:没有网关请求方等着,冷启动的"第一个请求慢"不再伤用户体验,模型加载慢一点无所谓。这是 Serverless 推理最舒服的用法之一,与部署架构模式里的"事件驱动模式"一致。

异步长任务:先回 task_id,再回调结果

推理超过 Lambda 同步超时预算(网关 29s / 函数最长 900s)时,改成异步两步:请求进来只负责排任务(写 SQS/DB),立即返回 task_id;Step Functions 编排的工作流(甚至带重试、条件分支)随后执行推理,完成后写结果、通知业务方。成本上异步与同步几乎一样,但把超时和重试都工程化了——这套模式同样适合接批处理推理管道的轻量场景。

四、成本模型:Lambda vs 常驻 GPU

先给结论:QPS 低于 1~5 的推理,Lambda 通常比常驻 GPU 实例便宜一个数量级;QPS 稳定高于 5~10,常驻实例+自动扩缩开始反超。做一次具体估算:

Lambda 计费 ≈ 调用次数 × (内存GB × 时长秒) × 单价(当前约 $0.00001667/GB·s,另有每百万次 $0.20 请求费)。一个 1024MB、平均耗时 2s 的推理函数,单次调用约 1GB × 2s × 0.00001667 ≈ $0.000033,即约 0.00023 元/次。

方案月成本(1 万次/月)月成本(300 万次/月)
AWS Lambda(1024MB,均耗 2s)约 $0.5约 $150
常驻 CPU 实例(2 核 4G,常开)约 $30约 $30(无限跑)
常驻 GPU 实例(A10,常开)约 $700+约 $700+

换算成 QPS 视角更直观:1 万次/月 ≈ 平均 0.004 QPS,300 万次/月 ≈ 平均 1.2 QPS。也就是说"平均 1 QPS 以下"的稳定业务,Lambda 在 300 万次/月这个量级仍然和常驻 CPU 实例打平;只有持续高于 1~5 QPS 的常流量,常驻 GPU 才真正省钱。成本估算别只算算力——常驻实例还要算维护、扩缩、闲置 GPU 的机会成本,Serverless 把这些都打包进单价了。

数据说明一切:低频下 Lambda 便宜 2~3 个数量级,高频下常驻实例摊薄到接近免费。拐点大约在"稳定 QPS ≈ 1~5"。完整容量与成本规划方法见性能优化与容量规划

五、限制与规避

限制数值规避
内存上限10240MB(10GB)模型 + 权重必须装进内存;大模型压缩/量化
超时上限900s(15 分钟)长任务转批处理或 Step Functions 异步编排
镜像大小上限10GB权重放 S3 按需拉取,或模型服务化拆开
ZIP 包上限250MB(解压后)用容器镜像(≤10GB)
并发上限区域级配额(默认 1000)预留并发 + 请求队列削峰
GPU不可用需要 GPU 就换容器平台(见下)
CPU 配额内存越高 CPU 越多推理密集任务加大内存档位

GPU 不可用是 Serverless 推理的最大边界:Lambda 无 GPU,超过几个 B 的模型、需要 GPU 实时推理的场景只能走带 GPU 的容器平台(AWS 的 SageMaker Serverless Inference、Google Cloud Run + GPU、阿里云函数计算 GPU 实例、自建 K8s + KEDA 按流量扩缩到 0)。这批平台计费同样是按调用/按扩缩,但保留了 GPU。

六、平台对比一句话定位

平台一句话定位
AWS Lambda生态最全、触发源最多,但无 GPU、限制最严
Google Cloud Run容器即服务,自动扩缩到 0,支持 GPU(Beta),冷启动快
阿里云函数计算(FC)国内生态好,支持 GPU 实例与自定义运行时
腾讯云 SCF与腾讯生态集成(微信、COS 触发),轻量好上手
自建 K8s + KEDA完全可控、可带 GPU,代价是自己运维"到 0"与冷启动

选型原则:先确认要不要 GPU——要 GPU 就在 Cloud Run/FC/K8s 里选;不要 GPU 且重度依赖云生态,Lambda 最省心。平台决策框架见框架与平台怎么选

常见坑与排查

现象排查
冷启动雪崩突发时首个请求 5~10s预留并发保底 + 异步化,见第三节
默认超时 3s模型还没加载完就超时调大 Timeout(模型加载类任务至少 30s)
内存不足 OOM权重加载 OutOfMemoryError调内存档位;权重 S3 惰性加载;量化
handler 报模块找不到Unable to import module容器镜像 WORKDIR/路径与 handler 声明一致
镜像超 10GB部署被拒权重从 S3 拉取,别全打镜像
并发超配额TooManyRequestsException预留并发 + 请求队列(SQS)削峰
GPU 模型放 Lambda运行报 no CUDA deviceLambda 无 GPU,换带 GPU 的容器平台

延伸阅读

参考资料