用并发扫描为 Amazon SageMaker AI 生成式模型端点精准定容

2026-09-22 14 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

为生成式 AI 端点选择实例数量,不能只看单次请求的延迟。真正决定容量的是:随着并发持续升高,吞吐量能否同步增长,P95/P99 延迟何时恶化,以及端点从哪个负载点开始出现排队、限流或错误。

Amazon SageMaker AI 的并发扫描方法,会在逐级增加负载的同时记录性能结果。结合 CreateAIBenchmarkJob API,可以把部署、压测和结果分析组织成可重复的容量评估流程,避免依靠一次手工测试或主观经验决定 fleet size。

并发扫描真正要找的是“拐点”

假设一个端点在不同并发下得到以下结果:

并发数 吞吐量(请求/秒) P95 延迟 错误率
1 0.8 1.3 秒 0%
2 1.5 1.5 秒 0%
4 2.8 1.9 秒 0%
8 4.1 3.8 秒 0%
16 4.3 8.6 秒 2.5%

并发从 1 增加到 8 时,吞吐量仍在增长;从 8 增加到 16 后,吞吐量几乎没有变化,但延迟和错误率快速上升。这里的并发 8 附近就是容量曲线的拐点,也可以理解为当前端点配置的饱和区域。

生产环境通常不应该运行在拐点上。更稳妥的做法是选择拐点之前、能够满足延迟目标的负载水平,并保留一定余量。例如,可以用下面的方式估算实例数:

实例数 = ceil(目标请求速率 / 单实例可持续请求速率 / 目标利用率)

如果单实例在满足 P95 延迟目标时可持续处理 3 RPS,业务峰值是 15 RPS,并希望利用率不超过 70%,则需要:

ceil(15 / 3 / 0.7) = 8 个实例

这里的“可持续请求速率”必须来自满足服务等级目标的测试点,而不是压到极限时出现的最高数字。

生成式 AI 压测不能只记录 RPS

传统 Web API 往往可以用请求数描述负载,但大模型请求的计算成本与输入、输出 token 数密切相关。两个同为 1 RPS 的流量模型,如果一个平均生成 32 token,另一个生成 512 token,对端点的压力完全不同。

设计 benchmark 时至少要固定或记录以下变量:

  • 模型、模型版本和推理容器版本;
  • 实例类型、实例数量以及是否启用了自动扩缩容;
  • 输入 prompt 的长度分布;
  • max_new_tokens、采样参数和实际输出长度;
  • 并发级别、每级持续时间和预热请求数量;
  • 平均延迟、P50、P95、P99、吞吐量和错误率;
  • 若使用流式推理,还应关注首 token 延迟和 token 生成速率。

测试期间如果允许自动扩缩容介入,不同并发级别可能对应不同实例数,结果就难以直接比较。评估单实例能力时,可以暂时固定容量;评估真实生产弹性时,则应保留自动扩缩容,并同时记录扩容时间和实例数量变化。这是两类不同实验,不宜混在一组结果中。

用 CreateAIBenchmarkJob 建立托管测试流程

CreateAIBenchmarkJob API 可以用于创建 AI benchmark job,并系统化执行并发扫描。由于 AWS CLI 和 SDK 中的请求字段可能随版本演进,实践时可以先让当前 CLI 生成与本地版本匹配的输入骨架:

aws --version

aws sagemaker create-ai-benchmark-job \
  --generate-cli-skeleton input \
  > benchmark-job.json

编辑 benchmark-job.json,按生成的字段填写 job 名称、执行角色、待测试模型或端点、测试数据位置、输出位置以及并发配置。随后提交任务:

aws sagemaker create-ai-benchmark-job \
  --cli-input-json file://benchmark-job.json

可以继续通过描述接口查看任务状态。具体返回字段以当前 AWS CLI 生成的模型为准:

aws sagemaker describe-ai-benchmark-job \
  --job-name YOUR_BENCHMARK_JOB_NAME

运行前需要确认三类权限:调用 benchmark API 的身份权限、benchmark job 使用的执行角色,以及该角色访问模型制品、测试数据和结果存储位置的权限。若 CLI 报告未知命令,应先升级 AWS CLI,并确认目标区域支持相应 API。

托管任务适合形成统一、可审计的基准流程。不过,在正式提交大规模任务前,用一个轻量客户端验证请求体、模型响应格式和候选并发范围,通常能更快发现配置问题。

一个可直接改造的端点并发扫描脚本

下面的 Python 脚本会调用已经部署好的 SageMaker 实时端点,依次测试多个并发级别,并输出吞吐量、延迟分位数和错误率。示例假设端点接受常见的 JSON 文本生成请求;如果模型容器使用其他协议,只需修改 PAYLOAD_JSON

保存为 sweep.py

import json
import math
import os
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

import boto3
from botocore.config import Config

ENDPOINT_NAME = os.environ["ENDPOINT_NAME"]
REGION = os.getenv("AWS_REGION", "us-east-1")
CONCURRENCY_LEVELS = [
    int(value) for value in os.getenv("CONCURRENCY", "1,2,4,8,16").split(",")
]
REQUESTS_PER_LEVEL = int(os.getenv("REQUESTS_PER_LEVEL", "40"))
WARMUP_REQUESTS = int(os.getenv("WARMUP_REQUESTS", "5"))

payload = json.loads(
    os.getenv(
        "PAYLOAD_JSON",
        json.dumps({
            "inputs": "Explain why concurrency testing matters for model serving.",
            "parameters": {"max_new_tokens": 128, "do_sample": False},
        }),
    )
)
body = json.dumps(payload).encode("utf-8")

runtime = boto3.client(
    "sagemaker-runtime",
    region_name=REGION,
    config=Config(
        max_pool_connections=max(CONCURRENCY_LEVELS) + 4,
        retries={"max_attempts": 1, "mode": "standard"},
    ),
)


def invoke_once():
    started = time.perf_counter()
    try:
        response = runtime.invoke_endpoint(
            EndpointName=ENDPOINT_NAME,
            ContentType="application/json",
            Accept="application/json",
            Body=body,
        )
        response["Body"].read()
        return {"ok": True, "latency": time.perf_counter() - started}
    except Exception as exc:
        return {
            "ok": False,
            "latency": time.perf_counter() - started,
            "error": type(exc).__name__,
        }


def percentile(values, p):
    if not values:
        return None
    ordered = sorted(values)
    index = max(0, math.ceil((p / 100) * len(ordered)) - 1)
    return ordered[index]


print(f"Warming up endpoint with {WARMUP_REQUESTS} requests...")
for _ in range(WARMUP_REQUESTS):
    invoke_once()

for concurrency in CONCURRENCY_LEVELS:
    started = time.perf_counter()
    with ThreadPoolExecutor(max_workers=concurrency) as pool:
        futures = [pool.submit(invoke_once) for _ in range(REQUESTS_PER_LEVEL)]
        results = [future.result() for future in as_completed(futures)]
    elapsed = time.perf_counter() - started

    successful = [item["latency"] for item in results if item["ok"]]
    errors = len(results) - len(successful)

    summary = {
        "concurrency": concurrency,
        "requests": len(results),
        "successful_requests": len(successful),
        "elapsed_seconds": round(elapsed, 3),
        "throughput_rps": round(len(successful) / elapsed, 3),
        "mean_latency_seconds": round(sum(successful) / len(successful), 3)
        if successful else None,
        "p50_latency_seconds": round(percentile(successful, 50), 3)
        if successful else None,
        "p95_latency_seconds": round(percentile(successful, 95), 3)
        if successful else None,
        "p99_latency_seconds": round(percentile(successful, 99), 3)
        if successful else None,
        "error_rate": round(errors / len(results), 4),
    }
    print(json.dumps(summary, ensure_ascii=False))

安装依赖并运行:

python -m pip install --upgrade boto3

export AWS_REGION=us-east-1
export ENDPOINT_NAME=my-generative-ai-endpoint
export CONCURRENCY=1,2,4,8,16
export REQUESTS_PER_LEVEL=50
export WARMUP_REQUESTS=5

python sweep.py | tee sweep-results.jsonl

如果端点请求格式不同,可以直接覆盖负载:

export PAYLOAD_JSON='{"prompt":"Write a short capacity planning checklist.","max_tokens":128}'
python sweep.py

这个脚本适合冒烟测试和确定合理的并发区间,但不能完全替代托管 benchmark。客户端所在网络、Python 线程调度和单台压测机的连接数都可能成为瓶颈。正式容量决策应结合 CreateAIBenchmarkJob 的结果以及 SageMaker 和 CloudWatch 中的端点指标。

从测试结果落到 fleet size

分析结果时,可以按以下顺序做决定:

  1. 先排除有错误、限流或 P95/P99 超过目标的并发级别。
  2. 在剩余结果中找到吞吐量增长开始明显放缓的位置。
  3. 选择拐点之前的可持续吞吐量,而不是理论峰值。
  4. 根据峰值业务流量、突发系数和目标利用率计算实例数。
  5. 用接近真实比例的短 prompt、长 prompt 和长输出重新验证。
  6. 对计划中的实例数量再做一次端到端测试,确认扩展后吞吐量是否近似线性增长。

并发扫描给出的不是一个永远有效的实例数,而是一条在特定模型、硬件、容器和请求分布下测得的容量曲线。模型版本、量化方式、推理镜像或 prompt 分布发生变化后,都应该重新运行 benchmark。把测试配置和结果纳入发布流程,才能让端点定容从一次性估算变成可重复的工程决策。


相关推荐