如何为 Amazon SageMaker AI 上的 TTS 推导自动扩缩容目标
Stop guessing auto scaling targets for TTS on Amazon SageMaker AI
文章介绍如何在 Amazon SageMaker AI 实时端点部署 Kokoro-82M,并用实测数据替代猜测来配置自动扩缩容。示例测得单实例并发上限 Q_max 为 50,计划扩容时间 T_total 为 320.2 秒,再据此推导扩缩容阈值和冷却时间。
文章把单实例并发上限与扩容耗时转化为自动扩缩容阈值和冷却时间,为延迟敏感、并发受限的实时端点提供了可复用的测量流程。
自托管文本转语音 (TTS) 端点遭遇流量激增,请求开始排队。就在最多用户正在收听时,延迟攀升,超过了服务级别目标 (SLO)。该端点背后的自动扩缩策略本应避免这种情况,但它所依据的目标值只是猜出来的,而这类猜测会悄无声息地失效,直到真实流量证明它们不准确。
这个数字要是估错了,就会以两种方式之一付出代价,而且都不会触发告警。估得过于保守,整个实例群会运行着大部分时间闲置的 GPU 实例。这笔成本会出现在每张账单上,却永远不会触发事件报告。估得过于激进,实例群就会承担无人察觉的风险。每个实例承载的并发请求超过了其在延迟承诺范围内能够处理的数量。只有真实流量激增、排队时间超过 SLO 时,问题才会显现。此时恰好是最多用户正在收听的时候。这就是隐藏的差距。在控制台上看似正常的策略,可能早已辜负了它本应服务的用户。
在本文中,我们将 Kokoro-82M(一款拥有 8200 万参数的 TTS 模型)作为自定义容器部署在 Amazon SageMaker AI 实时端点上。部署由 AWS Cloud Development Kit (AWS CDK) 堆栈负责。我们通过对实时端点进行两项测量,取代凭猜测设定参数。Q_max 是单个实例在违反 SLO 之前能够持续承载的最高并发数,T_total 是扩容从开始到完成所需的时间。我们根据这两个数字,通过计算得出扩容阈值、缩容阈值以及两个冷却时间。本文中的每个数字都来自对本文引用的 aws-samples 存储库进行的实际部署和测量。
为什么默认自动扩缩指标不适用于 TTS
大多数关于 Amazon SageMaker AI 自动扩缩的公开指导都针对短暂的同步请求/响应调用,或大型语言模型 (LLM) 服务;在这些场景中,自然适用的指标是每秒生成的令牌数。TTS 合成不属于这两种模式:单个请求会保持连接数秒,而且没有可关联指标的令牌生成循环。Kokoro-82M 是非自回归模型,是 StyleTTS2 的衍生模型,使用 ISTFTNet 解码器,通过对抗训练而非自回归的下一个令牌预测或迭代去噪进行训练。它通过一次与时长对齐的处理生成完整语句。根本不存在可用逐令牌指标进行监控的生成循环。判断 TTS 实例是否过载,实际比听起来简单:此刻有多少请求正在该实例上排队和执行。
部署的策略根据 ConcurrentRequestsPerModel 进行扩缩,这是 Amazon SageMaker AI 自身发布的原生指标,而不是必须由本项目的容器代码发出的指标。基于 Amazon SageMaker AI 自身发布的指标进行扩缩,可以消除一整类故障:自定义指标配置纸面上看似正确,实际上却没有任何内容发布到该指标。
ConcurrentRequestsPerModel 会发布 10 秒窗口内的 Maximum 统计值,这与基准测试客户端直接测量的平均在途请求数并非同一指标。工具会对此进行处理。在并发阶梯的每一级,它都会在相同负载下测量这两个值,并记录它们之间的比率。随后,它会用该比率将客户端测得的阈值转换为 Amazon CloudWatch 单位,再将其写入可部署配置。在本文的 Kokoro-82M 运行中,每一级测得的比率都恰好为 1.0,因此客户端测得的阈值与 Amazon CloudWatch 报告的阈值直接吻合。
解决方案概述
此解决方案会在 Amazon SageMaker AI 实时端点上通过自定义容器部署 Kokoro-82M,然后用基于针对该实时端点进行的两项测量得出的值,取代猜测的自动扩缩目标: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 堆栈只有在模型存在真实测得的 plan 后才会附加自动扩缩资源,因此,猜测得出的配置绝不会在无人察觉的情况下进入生产环境。本文余下内容将介绍这项测量。
先决条件
要按照本指南操作,你需要:
- An AWS account with permissions to create AWS Identity and Access Management (IAM) roles, Amazon Simple Storage Service (Amazon S3) buckets, Amazon Elastic Container Registry (Amazon ECR) repositories, and Amazon SageMaker AI resources.
- 注意:示例容器固定使用了较早版本的 PyTorch,该版本存在已知安全公告(
PYSEC-2026-2286,已在 torch 2.10.0 中修复)。在任何概念验证以外的用途之前,请检查并升级固定版本。
- 注意:示例容器固定使用了较早版本的 PyTorch,该版本存在已知安全公告(
- 已安装并在目标账户和区域中完成引导的 AWS CDK v2。
- Python 3.11 或更高版本、uv,以及用于构建容器映像的 Docker。
ml.g5.xlarge服务配额:运行本文中的扩展测量前,配额至少要比基准用量多出一个可用名额。- 本文所提及的 GitHub 存储库中的部署代码。
操作指南
完成本指南后,你将拥有一项用于 Kokoro-82M 的 Amazon SageMaker AI 实时自动扩缩策略;该策略完全基于针对你自己的端点进行的两项测量得出,而非凭空猜测。
部署端点堆栈
端点通过三个 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;它本身并不说明背后的数值是否经过测量。该堆栈会在合成时增加第二项独立检查:是否存在适用于此模型、确实可行的 plan 产物?只有两个条件都满足时,堆栈才会合成扩缩资源:一个用于接入扩缩策略和警报的 EndpointAutoscaling 构造,以及一个用于添加监控该策略所需仪表板和警报的 EndpointObservability 构造。具有 scaling_enabled=True 但没有测量数据支撑的模型,与具有 scaling_enabled=False 的模型一样,都会得到 no scaling resources 输出。
项目的 CLI 将三个堆栈封装在一个命令后,并按依赖顺序应用它们:
uv run speech-infra deploy kokoro-82mSpeechModelCacheStack 运行一个 AWS CodeBuild 项目,将模型的 Hugging Face 权重同步到缓存的 Amazon S3 存储桶中,此操作只进行一次。冷启动端点时,无需从互联网下载这些权重。在前面的 scaling_enabled 检查通过后,SpeechEndpointStack 会构建容器镜像、将其推送到 Amazon ECR,并创建 Amazon SageMaker AI 模型、端点配置和端点。
该方法打包为一套命令行工具链,按固定顺序运行:
图 3:按顺序运行的工具链,每条命令都将一个产物交给下一条命令
步骤 1:检查漂移
drift 每次都会首先运行,在执行任何需要花钱或让端点承受负载的操作之前完成。它会以只读方式对账实际部署的内容(可扩缩目标、扩缩策略、警报状态)与配置文件中声明的部署内容。否则,先前模型遗留的孤立策略,或已停止或取消的 qmax 或 ttotal 运行,可能会在测量期间悄然改变容量,导致测量数据失真。
uv run tts-bench drift如果结果正常,则会报告没有不匹配项,并以 preflight-ok 状态退出,为 qmax 扫清障碍。已停止或取消的 qmax 或 ttotal 运行是报告不匹配最常见的原因,因为它可能导致自动扩缩冻结在单个实例上。继续操作前,uv run tts-bench thaw --endpoint speech-kokoro-82m 会恢复已部署的策略,因为 qmax 和 ttotal 都假定已部署状态与配置文件完全一致。
步骤 2:测量 Q_max,即并发上限
Q_max 是最大并发数,即单个实例上排队请求与正在执行的请求总数;在此并发数下,请求仍能以第 95 百分位数在 SLO 时间内到达首个音频字节。测量采用闭环负载阶梯法:将并发数 N 精确保持在固定值,然后逐级提高 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 TTFAB(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。本文稍后介绍的另一个独立警报正是由这个数字触发。
第 3 步:测量 T_total,即实际扩容时间
T_total指从请求增加容量到新增容量开始承载健康流量所需的实际墙钟时间。测量时会直接强制扩容,绕过已部署策略自身的检测逻辑,然后为每个可区分的阶段记录时间戳。
uv run tts-bench ttotal --model kokoro-82m --qmax <qmax-artifact>ttotal需要将qmax工件作为输入,因为它的恢复判断依据该阶梯自身测得的各档数据,并且拒绝使用通过不同网络传输方式测得的阶梯运行。
强制扩容分解为以下八个测量阶段:
| 阶段 | 持续时间 | 占 T_total 的比例 |
| 请求容量至 desired_set | 30.0 s | 9.4% |
| desired_set 至实例出现在日志中 | 180.8 s | 56.5%(占主导) |
| 实例出现在日志中至容器启动 | 3.7 s | 1.2% |
| 容器启动至 framework_init | 0.0 s | 0.0% |
| framework_init 至 weights_ready | 6.5 s | 2.0% |
| weights_ready 至 warmup_done | 1.0 s | 0.3% |
| warmup_done 至 in_service | 20.7 s | 6.5% |
| in_service 至流量恢复 | 47.4 s | 14.8% |
| 测得总计 (t_total_s) | 260.2 s | |
| + 策略检测延迟上限(未测量) | 60.0 s | |
| 规划所依据的 T_total | 320.2 s |
图 5:实际扩容的八个测量阶段,从请求容量到流量恢复
占主导的阶段是 Amazon Elastic Compute Cloud (Amazon EC2) 实例配置加上容器镜像拉取,耗时 181 秒,占规划所依据总时间的 56%。要缩短T_total,最有效的方法不是修改应用程序代码,而是减小镜像体积或使用预热容量池。从“实例出现在日志中”之后的所有步骤加起来仅 79 秒,相对而言所占时间很少。
测得的 260.2 秒之外额外加上的 60 秒并非本次运行的测量值。它是根据策略自身的配置得出的上限(警报评估周期乘以评估周期数,再加上冷却时间),因为本次运行特意绕过策略的检测逻辑,以隔离纯粹的资源配置时间。plan根据完整的 320.2 秒为突增容量预留空间,因为这段响应时间是任何生产环境扩容过程的实际组成部分,尽管本次测量并未包含它。
第 4 步:通过 plan 推导已部署策略
plan是唯一不会接触 AWS 的命令。它读取qmax和ttotal工件,将它们与两个由运维人员选择的输入结合:3,000 ms p95 TTFAB 的 SLO,以及 1.25x 的突增比例(即在一个扩容周期内,流量增长 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, # trough 10 rps; 3 is the smallest safe floor for scale-in
max_instances=9, # peak 5000 rps
scaling_target_value=37.500, # C_scale_max, in Amazon CloudWatch units (ratio measured as 1.0)
scale_in_threshold=25.000, # C_scale_min, in Amazon CloudWatch units
ttfab_slo_ms=3000,
queue_max_depth=50, # enforced by the container's own admission gate
scale_out_cooldown_s=30, # short: one instance added at a time
scale_in_cooldown_s=960, # long: see below
)这两个冷却时间有意设置为不对称。缩容时判断失误的代价很低:最坏情况下,你只会为一个不需要的实例多付30秒的费用。扩容则不同,因为移除的容量如果之后又需要,替换它需要整整T_total(320秒)。缩容冷却时间设为约T_total的三倍,因此只有当负载确实持续下降时,才会触发缩容决策。
容器自身的准入控制会单独强制执行queue_max_depth=50,其值等于Q_max:超过该队列深度时,请求会立即被拒绝并返回503 queue_saturated响应,而不会继续排队到已无法满足 SLO 的程度。FirstChunkLatencyP95告警的阈值设为65 ms(并发数为1时测得的 p95 服务时间),用于监控已在处理请求的实例的服务时间;其阈值比端到端 SLO 更严格,因为后者包含排队时间。
Kokoro-82M 运行中有两项发现值得列出,因为它们体现了工具正确地指出一项已知且可接受的权衡,而非缺陷:
surge_survival(警告):将C_scale_max(37.5)设为Q_max(50)时,一旦触发扩容,单个实例的利用率就会达到97.4%,而替代实例需要整整T_total才能就绪。突发比率为队列额外预留了槽位,但要应对真正的流量激增,关键在于流量处理能力,也就是队列排空的速度;而在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制品的文件名都会编码测量时所依据的配置:将实例类型和容器镜像摘要浓缩为一个标识(例如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推导出的结果一致。任何不匹配都意味着已部署策略与测量所得方案出现了偏差;在依赖端点的自动扩缩行为之前,应先解决这一问题。
测试与验证
本文提及的每个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,即单个实例在违反 SLO 之前能够持续承载的最高并发量;以及 T_total,即一次实际扩容需要多长时间。根据这两个数值,通过计算得出扩缩阈值和两个冷却时间。使用 ConcurrentRequestsPerModel(AWS 原生发布的指标),并直接测量其单位换算,而不是凭空假设,才能确保此配置与实际并发量相匹配。
最终得到的是一项自动扩缩策略,其中每个数值都可追溯到针对你自己的端点进行的实际测量,而非从不同工作负载形态沿用的假设。这一区别能将配置文件中的猜测——可能在不知不觉中过度付费,或悄然违反 SLO——转变为一项有据可依的策略,其并发上限和扩容时间都有具体数值,且均经过测量,而非凭空断言。
这一包含四个数值的模型——选定 SLO 和突发比率,测量 Q_max 和 T_total,其余数值由此推导——并非 TTS 专属。它适用于对延迟敏感、受并发量限制的实时 Amazon SageMaker AI 端点。例如,实时图像嵌入端点在 GPU 前向传递期间会持续保持每个请求处于打开状态,因此受同一个并发上限限制,而非受吞吐量指标限制。同一条 qmax/ttotal/plan 链路也可直接沿用。此处真正的瓶颈是同时处理中的请求,而非从不同工作负载形态借用的吞吐量指标。这里的具体数值 Q_max=50 和 T_total=320s 适用于 ml.g5.xlarge 上的 Kokoro-82M。换用不同的模型或实例类型,数值也会不同,但得出这些数值的方法和工具不会变。五条命令组成的工具链 drift、qmax、ttotal、plan 和 validate,可以原样用于下一个以此方式部署的模型。
克隆示例存储库,然后针对你自己的端点运行由五条命令组成的链路 drift、qmax、ttotal、plan 和 validate,以生成经过测量的策略。查阅Amazon SageMaker AI 自动扩缩文档,根据你的 SLO 调整阈值,并打开 SageMaker AI 控制台,观察策略如何响应实时流量。如需帮助构建类似的、以测量为依据的部署,请联系你的 AWS 客户团队。
作者简介
Joseph Cottingham
Joseph 是 AWS Generative AI Innovation Center 的机器学习工程师,负责设计和交付 AI 系统,帮助组织从构想迈向生产。他的技术背景涵盖固件、软件和机器学习,尤其擅长高吞吐量、事件驱动型架构,确保 AI 工作负载在大规模运行时仍保持高性能。Joseph 专注于前沿研究与运营现实的交汇点,交付能够切实改变组织及其运营系统的解决方案。
来源:AWS Machine Learning Blog · aws.amazon.com




