普通语音转写只能回答“说了什么”,会议纪要、客服质检和访谈分析还需要知道“谁在什么时候说了什么”。AWS WhisperX Deep Learning Container 将 Whisper、基于 wav2vec2 的强制对齐,以及说话人分离封装进可使用 GPU 的镜像,可以部署为 Amazon SageMaker AI 实时端点或异步端点,输出词级时间戳和说话人标签。
真正进入生产环境后,难点不只在模型本身。GPU AMI 版本是否固定、请求应该走实时还是异步、端点能否缩容到零,以及音频和转写结果如何存放,都会直接影响稳定性与账单。
一条音频如何变成带说话人的词级结果
WhisperX 的处理链可以拆成三个阶段:
- Whisper 转写:识别音频内容并生成初步分段。
- wav2vec2 强制对齐:把文本重新对齐到音频,得到更精确的词级起止时间。
- 说话人分离:识别不同说话人的时间区间,再把说话人标签映射到单词或句段。
典型结果可以整理成下面这种结构:
{
"segments": [
{
"speaker": "SPEAKER_00",
"start": 0.82,
"end": 3.41,
"text": "今天我们讨论发布计划。",
"words": [
{"word": "今天", "start": 0.82, "end": 1.21, "speaker": "SPEAKER_00"},
{"word": "计划", "start": 2.93, "end": 3.41, "speaker": "SPEAKER_00"}
]
}
]
}
词级时间戳适合生成字幕、点击文字跳转音频、敏感词定位和通话质检。说话人标签通常只是会话内的匿名编号,并不等于身份识别;如果业务需要把 SPEAKER_00 映射到具体姓名,还需要额外的身份确认或声纹系统,并处理相应的隐私与合规问题。
实时端点还是异步端点
两种托管方式使用相同的模型镜像,但服务目标不同。
| 维度 | 实时端点 | 异步端点 |
|---|---|---|
| 适合场景 | 短音频、交互式应用、低延迟 API | 长录音、批量任务、后台处理 |
| 请求方式 | 客户端等待 HTTP 响应 | 输入放入 S3,任务排队执行,结果写回 S3 |
| 扩缩容 | 通常至少保留一个实例 | 可以配置自动缩容到零 |
| 成本特征 | 空闲时仍可能产生实例费用 | 突发任务和低频工作负载更经济 |
| 客户端设计 | 超时、重试和并发控制 | 任务状态、结果通知和幂等处理 |
如果输入是数十分钟的会议或访谈,不要为了复用同步 API 而把客户端超时拉得很长。异步推理更适合承接长音频:请求先进入队列,计算结束后再从 S3 获取结果。实时端点更适合长度受控、用户正在等待结果的语音片段。
还要注意,SageMaker 的“实时”表示同步请求模式,并不意味着 WhisperX 自动具备流式增量解码能力。若产品需要边说边出字,应单独评估切片、上下文拼接和流式 ASR 方案。
用 boto3 创建可切换模式的端点
下面的脚本可以直接改造成部署工具。运行前需要准备:
- 已推送或由 AWS 提供的 WhisperX DLC 镜像 URI;
- SageMaker 执行角色 ARN;
- 用于异步输入、输出的 S3 桶;
- 与镜像兼容、经过验证的 GPU 实例类型;
- 镜像要求的 GPU 推理 AMI 版本。
不要猜测 AMI 版本。将来源部署说明中经过验证的值显式写入 INFERENCE_AMI_VERSION,避免平台默认 AMI 更新后与镜像中的 CUDA、驱动或框架组合不兼容。
安装依赖:
python -m pip install --upgrade boto3
保存为 deploy_whisperx.py:
import os
import time
import boto3
MODE = os.getenv("MODE", "async") # async 或 realtime
REGION = os.environ["AWS_REGION"]
ROLE_ARN = os.environ["SAGEMAKER_ROLE_ARN"]
IMAGE_URI = os.environ["WHISPERX_IMAGE_URI"]
INSTANCE_TYPE = os.getenv("INSTANCE_TYPE", "ml.g5.xlarge")
AMI_VERSION = os.environ["INFERENCE_AMI_VERSION"]
S3_OUTPUT_PATH = os.getenv("S3_OUTPUT_PATH")
if MODE not in {"async", "realtime"}:
raise ValueError("MODE must be async or realtime")
if MODE == "async" and not S3_OUTPUT_PATH:
raise ValueError("S3_OUTPUT_PATH is required in async mode")
suffix = str(int(time.time()))
model_name = f"whisperx-{suffix}"
config_name = f"whisperx-{MODE}-{suffix}"
endpoint_name = os.getenv("ENDPOINT_NAME", f"whisperx-{MODE}")
sm = boto3.client("sagemaker", region_name=REGION)
sm.create_model(
ModelName=model_name,
ExecutionRoleArn=ROLE_ARN,
PrimaryContainer={
"Image": IMAGE_URI,
"Environment": {
# 变量名必须以所用 WhisperX 镜像的文档为准。
"HF_HOME": "/tmp/huggingface"
},
},
)
variant = {
"VariantName": "AllTraffic",
"ModelName": model_name,
"InitialInstanceCount": 1,
"InstanceType": INSTANCE_TYPE,
"InitialVariantWeight": 1.0,
"InferenceAmiVersion": AMI_VERSION,
"ContainerStartupHealthCheckTimeoutInSeconds": 900,
"ModelDataDownloadTimeoutInSeconds": 900,
}
request = {
"EndpointConfigName": config_name,
"ProductionVariants": [variant],
}
if MODE == "async":
request["AsyncInferenceConfig"] = {
"OutputConfig": {
"S3OutputPath": S3_OUTPUT_PATH
},
"ClientConfig": {
"MaxConcurrentInvocationsPerInstance": 1
},
}
sm.create_endpoint_config(**request)
try:
sm.describe_endpoint(EndpointName=endpoint_name)
except sm.exceptions.ClientError as exc:
if exc.response["Error"]["Code"] != "ValidationException":
raise
sm.create_endpoint(
EndpointName=endpoint_name,
EndpointConfigName=config_name,
)
else:
sm.update_endpoint(
EndpointName=endpoint_name,
EndpointConfigName=config_name,
)
print(f"Waiting for endpoint: {endpoint_name}")
sm.get_waiter("endpoint_in_service").wait(EndpointName=endpoint_name)
print(f"Endpoint is ready: {endpoint_name}")
异步部署示例:
export AWS_REGION=us-east-1
export SAGEMAKER_ROLE_ARN='arn:aws:iam::123456789012:role/SageMakerExecutionRole'
export WHISPERX_IMAGE_URI='123456789012.dkr.ecr.us-east-1.amazonaws.com/whisperx:validated-tag'
export INFERENCE_AMI_VERSION='替换为镜像文档要求的精确 AMI 版本'
export INSTANCE_TYPE='ml.g5.xlarge'
export S3_OUTPUT_PATH='s3://my-transcription-bucket/outputs/'
export ENDPOINT_NAME='whisperx-async'
export MODE='async'
python deploy_whisperx.py
脚本中的镜像 URI、实例类型和 AMI 值是部署输入,不应直接照抄占位内容。执行角色还必须拥有拉取 ECR 镜像、读取输入 S3 对象、写入输出路径以及写 CloudWatch Logs 的权限。
提交异步转写任务
异步推理要求请求体已经位于 S3。下面假设所用容器接受 JSON,并使用 audio_uri 指向音频文件;实际字段名和可选参数必须按照镜像的请求协议调整。
先创建请求文件并上传:
cat > request.json <<'JSON'
{
"audio_uri": "s3://my-transcription-bucket/audio/meeting.wav",
"language": "zh",
"diarize": true,
"word_timestamps": true,
"min_speakers": 2,
"max_speakers": 6
}
JSON
aws s3 cp request.json s3://my-transcription-bucket/requests/meeting.json
再通过 Python 提交任务:
import boto3
runtime = boto3.client("sagemaker-runtime", region_name="us-east-1")
response = runtime.invoke_endpoint_async(
EndpointName="whisperx-async",
InputLocation="s3://my-transcription-bucket/requests/meeting.json",
ContentType="application/json",
InferenceId="meeting-2025-001"
)
print("Output:", response["OutputLocation"])
print("Failure:", response.get("FailureLocation"))
InferenceId 应使用业务任务 ID 或幂等键,便于把 SageMaker 调用与数据库记录、日志和最终输出关联起来。生产系统不要依赖客户端不断高频轮询,可以结合 S3 事件、SNS 通知或任务表更新处理完成状态。
扩缩容与成本控制
异步端点最有价值的成本能力之一,是在没有积压请求时缩容到零。以下命令为现有异步端点注册可扩缩资源,并设置 0 到 4 个实例的范围:
aws application-autoscaling register-scalable-target \
--service-namespace sagemaker \
--resource-id endpoint/whisperx-async/variant/AllTraffic \
--scalable-dimension sagemaker:variant:DesiredInstanceCount \
--min-capacity 0 \
--max-capacity 4
aws application-autoscaling put-scaling-policy \
--policy-name whisperx-async-backlog \
--service-namespace sagemaker \
--resource-id endpoint/whisperx-async/variant/AllTraffic \
--scalable-dimension sagemaker:variant:DesiredInstanceCount \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 1.0,
"CustomizedMetricSpecification": {
"MetricName": "ApproximateBacklogSizePerInstance",
"Namespace": "AWS/SageMaker",
"Dimensions": [
{"Name": "EndpointName", "Value": "whisperx-async"}
],
"Statistic": "Average"
},
"ScaleInCooldown": 600,
"ScaleOutCooldown": 60
}'
阈值不能只看请求数量。十秒音频和两小时录音都算一个请求,但 GPU 占用时间完全不同。可以用真实流量做压测,记录音频时长、排队时间、处理时长、显存峰值和失败率,再调整:
MaxConcurrentInvocationsPerInstance:并发过高容易显存不足;过低则 GPU 利用率不佳。- 最大实例数:同时受预算、区域 GPU 配额和可用容量约束。
- 缩容冷却时间:过短会增加冷启动,过长会让空闲 GPU 持续计费。
- 音频长度上限:限制异常大文件,必要时预先切片。
- Whisper 模型尺寸与计算精度:更大的模型通常更准确,但显存、延迟和成本也更高。
即使允许缩容到零,第一个任务仍可能经历实例启动、容器加载模型和缓存初始化。对延迟敏感的工作负载应保留最小容量 1,或者将实时与异步端点分开:实时端点服务短请求,异步端点消化长音频和离线任务。
上线前的检查清单
- 固定镜像与 AMI:使用不可变镜像标签或 digest,并显式固定经过验证的 GPU AMI 版本。
- 验证输出质量:分别测试重叠发言、噪声、口音、多语言和静音片段;说话人数量提示并非总能得到正确结果。
- 保护数据:为 S3、ECR 和端点启用合适的加密与访问控制,定义音频和转写文本的保留周期。
- 控制失败重试:区分临时基础设施错误与坏音频,防止无法处理的任务无限重试。
- 监控四类指标:队列积压、端到端延迟、GPU/实例利用率、失败与超时数量。
- 准备回滚路径:更新端点配置前保留上一版模型、镜像 digest、AMI 和参数组合。
- 及时清理资源:实验结束后删除端点、端点配置和无用模型;仅删除模型对象不会停止端点计费。
WhisperX DLC 缩短了从模型组件到 GPU 推理服务的距离,但生产可用性来自端点模式、版本固定、扩缩容和数据治理的组合。短音频优先考虑实时端点,长音频和波峰明显的任务优先考虑异步端点;无论选择哪种方式,都应先用代表性录音验证准确率、延迟和单位音频成本,再决定实例规格与并发配置。