Stop guessing auto scaling targets for TTS on Amazon SageMaker AI

TL;DR · AI 摘要
Stop guessing auto scaling targets for TTS on Amazon SageMaker AI Artificial Intelligence Stop guessing auto scaling tar...
核心要点
- 主题聚焦:Stop guessing auto scaling targets for TTS on Am
- 来源:AWS Machine Learning Blog,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
停止猜测 Amazon SageMaker AI 上 TTS 的自动扩展目标 | 人工智能
停止猜测 Amazon SageMaker AI 上 TTS 的自动扩展目标
流量高峰冲击你自托管的文本到语音(TTS)端点,请求开始排队。当最多用户正在收听时,延迟超过服务等级目标(SLO)。该端点背后的自动扩展策略本应防止这种情况,但其扩展所依赖的目标值只是猜测,这类猜测在真实流量证明其错误前会悄无声息地失效。
如果这个数值设置错误,成本将通过两种方式体现,且都不会触发警报。如果预估过于保守,舰队将运行大量闲置的 GPU 实例。这种成本会出现在每张发票中,但不会触发事件报告。如果预估过于激进,舰队将承担无人察觉的风险。每个实例同时处理的请求数超过其延迟承诺的上限。直到真实流量高峰将排队时间推过 SLO 时,一切才看似正常。而这时正是最多用户收听的时刻。这就是隐藏的漏洞。控制台上看似正常的策略,可能已经无法为它本应服务的用户提供保障。
在本文中,我们部署 Kokoro-82M,这是一个 8200 万参数的 TTS 模型,作为 Amazon SageMaker AI 实时端点上的自定义容器。AWS 云开发工具包(AWS CDK)堆栈负责部署。我们用两个针对实时端点的测量值取代猜测:Q_max 是单个实例在突破 SLO 前能维持的最高并发数,T_total 是扩展操作从开始到结束的总耗时。我们通过算术运算从这两个数值中推导出扩展阈值、收缩阈值和两个冷却时间。本文中所有数值均来自本文提到的 aws-samples 仓库的实际部署和测量运行。
为什么默认的自动扩展指标不适用于 TTS
大多数关于 Amazon SageMaker AI 自动扩展目标的公开指南都侧重于同步请求/响应调用或大型语言模型(LLM)服务,这些场景的自然指标是每秒生成的 token 数。TTS 合成不符合任何一种模式:单个请求会保持连接数秒,没有 token 生成循环可供附加指标。Kokoro-82M 是非自回归模型,是 StyleTTS2 的衍生模型,采用 ISTFTNet 解码器,通过对抗训练而非自回归的下一个 token 预测或迭代去噪进行训练。它通过一次时长对齐的传递即可生成完整语句。从一开始就没有通过 token 指标进行监控的生成循环。判断 TTS 实例是否过载其实很简单:当前有多少请求正在排队和执行。
部署的策略基于 ConcurrentRequestsPerModel 指标,这是 Amazon SageMaker AI 本身发布的原生指标,而非本项目容器代码需要发出的指标。基于 Amazon SageMaker AI 发布的指标进行扩展,可以消除一类故障:自定义指标配置在纸面上看似正确,但实际上没有任何内容发布到该指标。
ConcurrentRequestsPerModel 在 10 秒窗口内发布最大值统计,这与基准测试客户端直接测量的平均在飞请求数是不同的指标。工具链已对此进行适配。在每级并发阶梯上,它会针对相同负载同时测量这两个指标并记录它们的比值。随后利用该比值将客户端测得的阈值转换为 Amazon CloudWatch 单位,再写入可部署的配置中。在本文的 Kokoro-82M 测试中,每个并发阶梯测得的比值恰好为 1.0,因此客户端测得的阈值与 Amazon CloudWatch 报告的阈值完全一致。
解决方案概览
该方案将 Kokoro-82M 部署在 Amazon SageMaker AI 实时端点的自定义容器中,随后用两个针对该实时端点的测量值替代猜测的自动扩展目标:Q_max(SLO 内单实例可处理的并发上限)和 T_total(实际横向扩展所需时间)。合成请求需经过四层架构:客户端、Amazon SageMaker AI 端点、端点后方 GPU 实例上的自定义容器,以及容器内的 Kokoro-82M 模型。
图 1:Kokoro-82M 请求路径,从客户端经 Amazon SageMaker AI 端点和自定义容器到达模型
该容器支持两种通信方式:响应流式传输(InvokeEndpointWithResponseStream),在音频合成过程中实时回传音频数据,使播放可在语句结束前启动;以及双向流式传输(InvokeEndpointWithBidirectionalStream),可保持长期会话以处理多条消息。
相较于使用托管推理工具包,将 Kokoro-82M 部署在自定义容器中是实现本文后续内容的关键。自主控制的容器可强制实施自身的准入限制,并暴露 Amazon SageMaker AI 已发布的精确并发信号,而非将不适用的借用指标强行套用于工作负载。AWS CDK 部署该端点的堆栈仅在存在真实测量计划时才附加自动扩展资源,因此猜测配置永远无法静默地进入生产环境。本文其余内容即为该测量过程。
先决条件
要完成本操作指南,您需要:
- 具有创建 AWS Identity and Access Management (IAM) 角色、Amazon Simple Storage Service (Amazon S3) 存储桶、Amazon Elastic Container Registry (Amazon ECR) 仓库和 Amazon SageMaker AI 资源权限的 AWS 账户。注意:示例容器固定了早期版本的 PyTorch(已知漏洞 PYSEC-2026-2286,已在 torch 2.10.0 中修复)。在非概念验证用途前请审查并升级该固定版本。
- 在目标账户和区域中已安装并初始化的 AWS CDK v2。
- Python 3.11 或更高版本、uv 工具和 Docker(用于构建容器镜像)。
- 在运行本文的横向扩展测量前,ml.g5.xlarge 服务配额需在基线之上至少预留一个空闲资源位。
- 本文引用的 GitHub 仓库中的部署代码。
该端点通过三个 AWS CDK 堆栈进行部署,按顺序应用:SpeechFoundationStack 创建 IAM 角色和用于缓存模型权重的 Amazon S3 存储桶,SpeechModelCacheStack 将模型的 Hugging Face 权重一次性同步到该存储桶,SpeechEndpointStack 构建容器镜像、推送到 Amazon ECR,并创建 Amazon SageMaker AI 模型、端点配置和端点。
图 2:三个 AWS CDK 堆栈和决定自动扩展资源是否被合成的门禁机制
第三个堆栈上的门禁机制阻止默认的、未经过测量的扩展配置进入生产环境。scaling_enabled 是一个静态标志,当 max_instances 配置值大于 min_instances 时为 true,但它本身并不说明背后的数值是否曾经被测量过。堆栈在合成时增加了一个独立的二次检查:是否存在该模型的真实可行计划工件?只有当这两个条件同时满足时,堆栈才会合成扩展资源:一个 EndpointAutoscaling 构造将扩展策略和告警关联起来,以及一个 EndpointObservability 构造添加用于监控的仪表板和告警。一个 scaling_enabled=True 但背后没有测量数据的模型,与 scaling_enabled=False 的模型一样,都会输出无扩展资源的结果。
该项目的 CLI 命令将三个堆栈封装在单个命令下,按依赖顺序依次应用:
uv run speech-infra deploy kokoro-82mSpeechModelCacheStack 运行一个 AWS CodeBuild 项目,将模型的 Hugging Face 权重一次性同步到缓存的 Amazon S3 存储桶。冷启动端点无需从互联网拉取这些权重。受前面 scaling_enabled 检查的限制,SpeechEndpointStack 构建容器镜像、推送到 Amazon ECR,并创建 Amazon SageMaker AI 模型、端点配置和端点。
该方法论封装为一个命令行工具链,按固定顺序运行:
图 3:工具链按顺序运行,每个命令将工件传递给下一个步骤
第一步:检查漂移
drift 命令始终在任何涉及成本或承载端点负载的操作之前首先运行。它是一个只读的对齐检查,比较实际部署内容(可扩展目标、扩展策略、告警状态)与配置文件中声明的应部署内容。如果之前模型遗留的孤立策略,或被中止的 qmax 或 ttotal 运行未被处理,可能会在测量过程中静默改变容量并破坏测量数据。
uv run tts-bench drift干净的结果会报告无不匹配项并以 preflight-ok 状态退出,为 qmax 做好准备。被中止或取消的 qmax 或 ttotal 运行是最常见的不匹配报告来源,因为它可能导致自动扩展冻结在单个实例上。在继续操作前,uv run tts-bench thaw --endpoint speech-kokoro-82m 会恢复已部署的策略,因为 qmax 和 ttotal 都假设部署状态与配置文件完全一致。
第二步:测量 Q_max,即并发上限
Q_max 是指在 95 分位数下,请求仍能在 SLO 内首次获取音频字节的最大并发量。该指标通过闭环负载阶梯测试测量:固定并发量 N,逐步递增直到 SLO 被打破。闭环测试避免了固定到达率测试中的“协调遗漏”缺陷,即服务器变慢时客户端会减少请求数量而非暴露真实的排队延迟。
uv run tts-bench qmax --model kokoro-82m --dry-run
uv run tts-bench qmax --model kokoro-82m--dry-run 会打印调度计划和零 AWS 调用的预估耗时,让您先预估实际运行约 36 分钟的成本。实际运行命令会冻结自动扩展并锁定单实例容量,退出后自动恢复,因此输出的数值是真实的单实例上限。
负载阶梯测试记录了以下结果:
并发量
p95 首字节时间 (ms)
是否满足 3,000 ms SLO
1
65.4
是
5
286.2
10
568.5
20
1,129.3
30
1,689.8
40
2,245.0
50
2,805.7
是 (
Q_max)
60
3,374.8
否(超出)
图 4:p95 首字节时间与并发量关系,包含 SLO 线和并发量 60 时的超出点
Q_max = 50,这是一个区间结果:第 60 级实际上突破了 SLO,这使得 50 成为真实上限而非下限。同一测试还测量了无竞争服务时间,即无任何排队时单个请求耗时:平均 61.3 ms,p95 66.1 ms。这个数值驱动了本文后续描述的第二个独立告警机制。
第三步:测量 T_total,真实的扩展耗时
T_total 是从请求增加容量到容量开始处理健康流量所需的墙钟时间。通过直接强制扩展(绕过部署策略的检测逻辑)并为每个可区分阶段添加时间戳来测量。
uv run tts-bench ttotal --model kokoro-82m --qmax <qmax-artifact>ttotal 需要 qmax 的测试结果作为输入,因为其恢复判断依赖该阶梯测试的测量数据,并拒绝使用不同网络传输方式测量的阶梯数据。
强制扩展分解为八个测量阶段:
阶段
耗时
占 T_total 比例
T_total请求容量到期望容量
30.0 秒
9.4%
期望容量到实例在日志中可见
180.8 秒
56.5%(主导)
实例可见到容器启动
3.7 秒
1.2%
容器启动到框架初始化
0.0 秒
0.0%
框架初始化到权重就绪
6.5 秒
2.0%
权重就绪到预热完成
1.0 秒
0.3%
预热完成到服务可用
20.7 秒
6.5%
服务可用到流量恢复
47.4 秒
14.8%
总耗时(t_total_s)
260.2 秒
+ 策略检测延迟(未测量,上限 60 秒)
计划总耗时(T_total)
320.2 秒
图 5:真实扩展过程的八个测量阶段,从容量请求到流量恢复
主导阶段是亚马逊弹性计算云(Amazon EC2)实例配置和容器镜像拉取,耗时 181 秒,占计划总耗时的 56%。减少 T_total 最有效的方式不是应用代码优化,而是减小镜像体积或使用预热容量池。所有“实例在日志中可见”之后的阶段合计仅耗时 79 秒,相对影响较小。
在此次运行中,测量值为260.2秒的基础上额外增加的60秒未被计入。这一限制来源于策略本身的配置(告警评估周期乘以评估周期数,加上冷却时间),因为此次运行有意绕过了策略的检测逻辑,以隔离纯粹的资源配置时间。计划规模在320.2秒总时长中留有余量,因为该反应时间是任何生产规模扩展的真实组成部分,尽管此次测量排除了它。
第4步:通过计划推导部署的策略
plan 是唯一一个从不接触 AWS 的命令。它读取 qmax 和 ttotal 的工件,将它们与两个操作员选择的输入相结合:95% 分位数的 3,000 毫秒 TTFAB SLO 和 1.25 倍的突增比例(舰队在单次扩展周期内可容忍流量增长 25% 而不违反 SLO),然后执行以下计算。它随后会打印一份报告,对每个发现给出判断结果:ok、warn,或者对于真正不可行的计划,给出 stop。
uv run tts-bench plan --qmax <qmax-artifact> --ttotal <ttotal-artifact> --peak-rps 5000--peak-rps 标志(此处为每秒 5,000 个请求,假设低谷为 10)确定舰队的实例数量。它不参与下方的阈值计算。
h = surge_ratio - 1 = 1.25 - 1 = 0.25
C_scale_max = (1 - h) x Q_max = 0.75 x 50 = 37.50
C_scale_min = (1 - 2h) x Q_max = 0.50 x 50 = 25.00
W_max = SLO - S_p95 = 3.000s - 0.066s = 2.934sC_scale_max 是添加一个实例时的并发量,低于 Q_max 的突增比例 h,因此在请求更多容量时,舰队仍保留队列余量。C_scale_min 是移除一个实例时的并发量,设置为 Q_max 的 (1 - 2h),因此只有在负载明显低于可能再次触发扩展的水平时,才会触发缩容。
最终部署的配置,与工具生成的结果完全一致:
"kokoro-82m": ModelEndpointConfig(
model_name="kokoro-82m",
instance_type="ml.g5.xlarge",
min_instances=1, # 低谷 10 rps;3 是缩容的安全最小值
max_instances=9, # 高峰 5000 rps
scaling_target_value=37.500, # C_scale_max,以 Amazon CloudWatch 单位(1.0 为基准比值)表示
scale_in_threshold=25.000, # C_scale_min,以 Amazon CloudWatch 单位表示
ttfab_slo_ms=3000,
queue_max_depth=50, # 由容器自身的准入门控强制执行
scale_out_cooldown_s=30, # 短:一次添加一个实例
scale_in_cooldown_s=960, # 长:见下文
)两个冷却时间故意设置为不对称。扩展时出错成本较低:最坏情况是多支付了 30 秒的单个实例费用。缩容时成本不对称,因为移除容量后再次需要时需花费完整的 T_total(320 秒)来恢复。缩容冷却时间设为约三倍 T_total,因此只有在负载真正、持续下降时才会触发缩容。
容器自身的准入控制独立强制执行 queue_max_depth=50,等于 Q_max:超过该深度时,请求会立即拒绝并返回 503 queue_saturated 响应,而不是排队到无法满足 SLO 的程度。一个 FirstChunkLatencyP95 告警,阈值设为 65 毫秒(并发量为 1 时测得的 p95 服务时间),监控已服务实例的响应时间,比包含排队的端到端 SLO 更严格。
两个来自 Kokoro-82M 运行的重要发现值得记录,因为它们体现了工具正确揭示了一个已知且被接受的权衡,而非缺陷:
- surge_survival(警告):将 C_scale_max(37.5)与 Q_max(50)对比时,当扩容触发的瞬间,单个实例会达到 97.4% 的利用率,而新实例需要完整的 T_total 时间才能到位。虽然 surge 比率保留了额外的队列槽位,但真正应对突发流量的关键在于流量处理速度和队列清空效率,在 97.4% 利用率下几乎没有任何流量缓冲空间。
- scale_in_safety(警告):从 N 个实例的集群中移除一个实例,会使剩余每个实例的并发能力乘以 N/(N-1)。只有当这个比率低于 C_scale_max/C_scale_min(此处为 1.5)时,缩容才能保持稳定,这要求 N ≥ 3。若最低保留 1 个实例,从 2 个实例缩容到 1 个实例时,剩余实例会直接达到 Q_max,重新触发扩容机制。较长的冷却时间会抑制这种震荡但无法彻底消除。若将最低保留数提高到 3,虽然会增加低流量时的冗余容量,但能从结构上消除此问题。
每个 qmax 或 ttotal 工件的文件名都编码了其对应的配置信息,实例类型和容器镜像摘要被压缩成 slug 格式(例如:g5xlarge-8b1b96bc)。ttotal 和 plan 命令会拒绝配对指纹不一致的工件。此行为可通过 --allow-config-mismatch 降级为警告,但默认行为是直接拒绝。
第 5 步:使用 validate 确认已部署的策略
validate 是链式命令中的最后一个命令,也是与 drift 并列的第二个不会修改任何内容的命令。它会将实际部署的扩展策略与 plan 工件中的值进行对比,在将配置视为生效前确认两者匹配。
uv run tts-bench validate --model kokoro-82m --plan <plan-artifact>干净的验证结果会确认已部署的可扩展目标上的 scaling_target_value、scale_in_threshold 以及两个冷却时间与 plan 推导出的值一致。任何不匹配都意味着已部署的策略与测量的 plan 已产生偏差,应在依赖端点自动扩展行为前解决此问题。
测试与验证
本文提及的所有 qmax、ttotal 和 plan 工件均为仓库中已提交的文件,而非一次性本地输出。前述示例(包括所有数字)均可通过重新运行 plan 命令,针对两个已提交的测量文件进行独立复现,无需任何 AWS 调用且不会产生新费用:
$ uv run tts-bench plan --qmax <qmax-artifact> --ttotal <ttotal-artifact> --peak-rps 5000
scaling_target_value : 37.500 (C_scale_max)
scale_in_threshold : 25.000 (C_scale_min)
surge_survival : warn
scale_in_safety : warn清理
要删除本操作指南中创建的资源,请使用单条命令按相反顺序拆除堆栈:
uv run speech-infra destroy kokoro-82mspeech-infra destroy 会删除 Amazon SageMaker AI 模型、端点配置、端点以及所有关联的自动扩展或 Amazon CloudWatch 资源。它不会清空缓存模型权重的 Amazon S3 存储桶,也不会删除存储容器镜像的 Amazon ECR 仓库,因为这些资源可能被多个模型共享。如果不再需要这些资源,请手动清空并删除 Amazon S3 存储桶,以及移除 Amazon ECR 仓库,因为存储的模型检查点和容器镜像在删除前仍会产生小额存储费用。
结论
部署 Kokoro-82M 为 Amazon SageMaker AI 实时端点上的自定义容器,通过在实时端点上进行两次测量(Q_max 和 T_total)取代猜测:Q_max 是单个实例在违反 SLO 前能维持的最高并发数,T_total 是实际扩容所需的时间。通过简单的数学运算,即可从这两个数值推导出扩容阈值、缩容阈值及两个冷却时间。基于 AWS 官方指标 ConcurrentRequestsPerModel 进行并发数测量,直接验证单位换算关系而非假设,这使配置始终与真实并发数保持一致。
最终形成的自动扩展策略中,每个参数都可追溯到对您自身端点的真实测量数据,而非从其他工作负载形态继承的假设值。这种差异使原本依赖配置文件猜测(可能静默超支或静默违反 SLO)的策略,转变为可通过具体并发上限和具体扩容时间(均经测量而非假设)进行防御的策略。
这套四参数模型(SLO 和突增比例由用户选择,Q_max 和 T_total 通过测量获得,其余参数通过计算得出)并不特定于 TTS 场景。它可推广至所有延迟敏感、受并发限制的实时 Amazon SageMaker AI 端点。例如,一个实时图像嵌入端点若需通过 GPU 前向传播保持每个请求开放,其瓶颈同样是并发上限而非吞吐量指标。相同的 qmax/ttotal/plan 逻辑链同样适用。该场景的真实瓶颈是并发进行中的请求数,而非从其他工作负载形态借用的吞吐量指标。此处的具体数值 Q_max=50 和 T_total=320s 适用于 Kokoro-82M 在 ml.g5.xlarge 实例上的表现,不同模型或实例类型会得出不同数值。但产生这些数值的方法论和工具链保持通用。由 drift、qmax、ttotal、plan 和 validate 五个命令组成的工具链,在部署新模型时无需修改即可复用。
克隆示例仓库,然后将 drift、qmax、ttotal、plan 和 validate 五命令链应用于您自己的端点,生成基于测量的策略。参考 Amazon SageMaker AI 自动扩展文档调整阈值以适配您的 SLO,然后在 SageMaker AI 控制台观察策略对实时流量的响应。如需帮助构建类似的测量驱动型部署,请联系您的 AWS 账户团队。
关于作者
'"`