用端点实测数据制定语音模型扩缩容策略
文章以自定义容器部署 Kokoro-82M 到 SageMaker AI,利用在线端点的 Q_max 和 T_total 两项实测值推导自动扩缩容策略。目标是减少阈值猜测带来的 GPU 浪费与延迟 SLO 违约。
AI 深度解读
TTS 接口一遇到流量高峰就排队,未必是自动扩容没开,也可能是触发阈值从一开始就设错了。设低了,GPU 长期闲着;设高了,新实例还没就绪,用户等待首段音频的时间已经超标。
AWS 这篇文章用 Kokoro-82M 的一次实际部署,演示如何测出单实例并发上限和扩容耗时,再计算伸缩参数。对接语音服务的前端、Node 和全栈开发者,最值得看的是参数背后的证据,以及工具明确报出的两项风险。
TTS 扩容为什么要看正在处理的请求数
Kokoro-82M 有 8200 万参数,是采用 ISTFTNet 解码器的 StyleTTS2 衍生模型。它通过一次按时长对齐的计算生成整段语音,不走自回归逐 token 生成流程。因此,直接借用 LLM 的每秒 token 数衡量负载,找不到对应的生成循环。
请求却会持续占用连接。判断实例是否吃紧,更直接的指标是:当前有多少请求正在执行,还有多少正在排队。
作者选择 SageMaker AI 原生发布的 ConcurrentRequestsPerModel,避免自行上报指标时出现「策略配置好了,指标却没人发」的问题。不过,它统计的是 10 秒窗口内的 Maximum,不能直接当成压测客户端看到的平均在途请求数。
工具在每一级并发下同时测量这两个量,再按比例换算阈值。这次实验的比例恰好处处为 1.0,所以数值可以直接对应。换一个模型或请求形态,仍需重新验证。
传输方式也属于实验条件。容器支持返回音频流的 InvokeEndpointWithResponseStream,以及在一个长连接内交换多条消息的 InvokeEndpointWithBidirectionalStream。工具拒绝把不同传输方式的压测结果混用,因为一次会话和一次请求占用资源的方式可能不同。
先测容量,再测新容量多久能到
部署通过三个 AWS CDK 栈完成:SpeechFoundationStack 创建权限和缓存桶,SpeechModelCacheStack 把 Hugging Face 权重同步到 Amazon S3,SpeechEndpointStack 构建并推送容器,创建推理端点。入口命令是 uv run speech-infra deploy kokoro-82m。
这里有个适合借鉴的约束:即使 max_instances > min_instances、scaling_enabled=True,没有真实且可行的测量计划,第三个栈也不会生成自动伸缩资源。
测量分两步。
第一步测 Q_max,即单实例仍能满足 p95 首音频字节时间(TTFAB)目标的最高并发,包括执行中和排队中的请求。先运行只读检查 uv run tts-bench drift,确认线上策略与配置一致,再执行 uv run tts-bench qmax --model kokoro-82m。加上 --dry-run 可以零 AWS 调用预览计划,实际压测约需 36 分钟。
工具暂时冻结伸缩,把容量固定为一个实例,逐级增加并发。这次并发 50 仍达标,并发 60 已超标,因此得到 Q_max=50。由于确实测到了失败档位,50 才有明确的边界依据。无排队时,服务时间均值为 61.3 ms,p95 为 66.1 ms。
第二步用 uv run tts-bench ttotal --model kokoro-82m --qmax <qmax-artifact>,强制扩容并测量新实例何时恢复健康服务。
实测过程耗时 260.2 秒。其中 EC2 实例供应和镜像拉取占 181 秒,约为规划总时长的 56%;实例出现在日志之后的阶段合计约 79 秒。这里优先缩小镜像、评估预热容量,比只盯着应用代码更有针对性。
规划还要加上策略检测与冷却的 60 秒,最终按 T_total=320.2 秒 计算。**这 60 秒来自策略配置推算,并非本次强制扩容实测。**作者绕过了检测逻辑,所以必须把它补回来。
阈值算得出来,突发风险仍然存在
除了两个测量值,还需人工选择两个输入:p95 TTFAB 的 SLO 为 3000 ms,突发比例为 1.25x,希望覆盖一个扩容周期内 25% 的流量增长。
下面的计算把并发上限转换为扩缩容阈值,并求出理论排队预算:
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.934s
要看的是前两条阈值公式:并发到 37.5 就扩容,降到 25 才缩容,给等待新实例和避免反复伸缩留出空间。W_max=2.934 秒 是延迟预算,不能当作队列已经安全的证明。
运行 uv run tts-bench plan --qmax <qmax-artifact> --ttotal <ttotal-artifact> --peak-rps 5000,工具会生成配置。--peak-rps 5000 和假设的低谷 10 rps 用于规划实例数量,不参与上面的阈值计算。
下面是原文生成的配置,注释保留了最小实例数的取舍:
"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,CloudWatch 单位,实测换算比例为 1.0
scale_in_threshold=25.000, # C_scale_min,CloudWatch 单位
ttfab_slo_ms=3000,
queue_max_depth=50, # 由容器自己的准入控制执行
scale_out_cooldown_s=30, # 扩容冷却较短,每次增加一个实例
scale_in_cooldown_s=960, # 缩容冷却较长
)
要特别看 min_instances=1:它是成本选择,不代表缩容稳定性已经解决。960 秒的缩容冷却约为三倍 T_total,目的是避免刚删掉容量,又等约 320 秒补回来。扩容冷却则为 30 秒。
容器还设置了 queue_max_depth=50 的准入限制,超过限制立即返回 503 queue_saturated,避免继续接收已难满足 SLO 的请求。
工具仍然给出两项 warn。其一,触发扩容时实例利用率已达 97.4%。队列还有位置,不代表服务有足够速度消化新增流量。其二,N 个实例缩成 N−1 个,每个剩余实例的并发会放大 N/(N-1);按阈值比 37.5/25=1.5,稳定缩容要求 N≥3。从 2 缩到 1,会让剩余实例回到 Q_max。长冷却只能缓解震荡,把下限升到 3 才能消除这里指出的结构性问题。
我的判断
我更认可这套流程对配置来源的约束:测量文件带有实例类型和镜像摘要指纹,ttotal、plan 默认拒绝混用不匹配的结果,只有显式传入 --allow-config-mismatch 才降为警告。最后用 uv run tts-bench validate --model kokoro-82m --plan <plan-artifact> 检查线上阈值和冷却时间是否与计划一致。相比复制默认值,这让配置有了可追溯的依据。
但它没有证明这组参数能扛住真实突发。作者没有提供与 CPU、GPU 利用率或请求速率策略的完整对照,也没有给出足以消除 surge_survival 警告的验证结果。5000 rps 是规划输入,不能读成线上峰值承载能力已经验收。
原文关于闭环压测避免 coordinated omission 的表述也应保留疑问。固定并发适合寻找容量边界,但服务变慢时,请求发起速率也会下降,不能仅凭闭环方式就断言没有遗漏真实等待。持续到达的突发流量仍需另测。
还有一个数字没解释清楚:正文给出无竞争 p95 服务时间 66.1 ms,却把 FirstChunkLatencyP95 告警阈值写成 65 ms,并称其对应实测值。照搬前应核对原始产物。
这套方法可以迁移到并发受限的实时 GPU 推理接口,但 Q_max=50 和约 320 秒只属于此次 Kokoro-82M、ml.g5.xlarge 的实验条件。
能拿来做什么
- 为语音接口建立独立的首段音频延迟指标,并把它与整段生成完成时间分开观察。Node 服务对接时,应明确处理
503 queue_saturated,避免立即重试继续推高队列。 - 将
drift → qmax → ttotal → plan → validate固化成部署流程。压测中断若留下冻结状态,可用uv run tts-bench thaw --endpoint speech-kokoro-82m恢复,再检查一致性。 - 在模型、镜像、实例规格或传输方式变化后重新测量,并额外验证突发与缩容场景。若业务不能接受从 2 缩到 1 后再次扩容的等待,应评估把最小实例数设为 3 的常驻成本。
- 复现前准备 AWS CDK v2、Python 3.11 或更高版本、uv、Docker,以及比基线多一个名额的
ml.g5.xlarge配额。原文提示样例 PyTorch 涉及PYSEC-2026-2286,修复版本为torch 2.10.0,非概念验证用途前应升级。结束后运行uv run speech-infra destroy kokoro-82m,共享的 S3 缓存桶和 ECR 仓库还需按需手动清理。